基础排序算法总结
来源:互联网 发布:telent的端口号 编辑:程序博客网 时间:2024/06/14 10:59
八中算法的稳定性问题分析
通俗的讲就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后他们两个的前后位置顺序相同。简化形式为:如果
稳定性的好处:排序算法如果是稳定的,那么从一个健上排序,然后再从另一个健上排序,第一个排排序的结果可以为第二个健排序苏勇。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序在高位也相同时是不会改变的。
冒泡排序
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
template<typename T> void bubble_sort(T arr[], int len) { int i, j; for (i = 0; i < len - 1; i++) for (j = 0; j < len - 1 - i; j++) if (arr[j] > arr[j + 1]) swap(arr[j], arr[j + 1]);}
1.最坏时间复杂度
2. 最优时间复杂度
3. 平均时间复杂度
4. 空间复杂度 :总共
在最坏的情况下冒泡排序需要
一种变形为鸡尾酒排序:也就是定向冒泡排序,以双向在序列中进行排序。不同:不同的地方在于从低到高然后从高到低,而冒泡排序则仅从低到高去比较序列里的每个元素。他可以得到比冒泡排序稍微好一点的效能,原因是冒泡排序只从一个方向进行比对(由低到高),每次循环只移动一个项目。
void cocktail_sort(int arr[], int len){ int j, left = 0, right = len - 1; while(left < right){ for(j = left; j < right; j++){ if(arr[j] > arr[j+1]) swap(arr[j],arr[j+1]); } right--; for(j = right; j > left; j--) if(arr[j-1] > arr[j]) swap(arr[j-1],arr[j]); left++; } }
鸡尾酒排序最糟或是平均所花费的次数都是
选择排序
选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n - 1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。
原理:搜先在未排序的序列中找到最小(大)元素,放在排序序列的起始位置,然后再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的尾部。
优点:主要与数据移动有关,如果某个元素位于正确的最终位置上,它不会被移动。选择排序每次交换一对元素,他们当中至少有一个将被移动到其最终位置上,因此对n个元素的表进行排序总共进行最多n-1次交换。在所有完全依靠交换区移动元素的排序算法中,选择排序属于非常好的一种。
template<typename T> void selection_sort(std::vector<T>& arr) { for (int i = 0; i < arr.size() - 1; i++) { int min = i; for (int j = i + 1; j < arr.size(); j++) if (arr[j] < arr[min]) min = j; std::swap(arr[i], arr[min]); }}
1.最坏时间复杂度
2. 最优时间复杂度
3. 平均时间复杂度
4. 空间复杂度 :总共
选择排序的交换操作介于
比较次数
插入排序
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
原理:通过构建有序序列,对于未排序数据,在已排序序列中以后向前扫描,找到相应的位置并插入,插入排序在实现上,通常采用inplace排序(只需要用到
描述:
1.从第一个元素开始,该元素可以认为已经被排序
2.取出下一个元素,在已经排序的元素序列中从后向前扫描
3.如果该元素(已排序)大于新元素,将该元素移到下一位置
4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5.将新元素插入到该位置后
6.重复步骤2~5
如果比较操作的代价比交换操作大的话,可以采用二分查找法来减少比较操作的数目。该算法可以认为是插入排序的一个变种,称为二分查找插入排序。
void insertion_sort(int arr[], int len) { for (int i = 1, j = 0 ; i < len; ++i)//对比len - 1次,直到最后只剩一个数 { int temp = arr[i]; //temp为关键数 for (j = i - 1; j >= 0 && arr[j] > temp; --j)//对比关键数前的所有数 arr[j + 1] = arr[j]; //找到比关键数大的数值,插入到关键数后面 arr[j + 1] = temp; //插入关键数 }}
1.最坏时间复杂度
2. 最优时间复杂度
3. 平均时间复杂度
4. 空间复杂度 :总共
如果目标是把
二分查找插入排序
原理:是直接插入排序的一个变种,区别是:在有序区中查找新元素插入位置时,为了减少元素比较次数提高效率,采用二分查找算法进行插入位置的确定。
实现:
设数组为a[0…n]。
1. 将原序列分成有序区和无序区。a[0…i-1]为有序区,a[i…n] 为无序区。(i从1开始)
2. 从无序区中取出第一个元素,即a[i],使用二分查找算法在有序区中查找要插入的位置索引j。
3. 将a[j]到a[i-1]的元素后移,并将a[i]赋值给a[j]。
4. 重复步骤2~3,直到无序区元素为0。
/*二分查找函数,返回插入下标*/template <typename T>int BinarySearch(T array[], int start, int end, T k){ while (start <= end) { int middle = (start + end) / 2; int middleData = array[middle]; if (middleData > k) { end = middle - 1; } else start = middle + 1; } return start;}//二叉查找插入排序template <typename T>void InsertSort(T array[], int length){ if (array == nullptr || length < 0) return; int i, j; for (i = 1; i < length; i++) { if (array[i]<array[i - 1]) { int temp = array[i]; int insertIndex = BinarySearch(array, 0,i, array[i]);//使用二分查找在有序序列中进行查找,获取插入下标 for (j = i - 1; j>=insertIndex; j--) //移动元素 { array[j + 1] = array[j]; } array[insertIndex] = temp; //插入元素 } }}
- 时间复杂度:O(n^2):二分查找插入位置,因为不是查找相等值,而是基于比较查插入合适的位置,所以必须查到最后一个元素才知道插入位置。二分查找最坏时间复杂度:当2^X>=n时,查询结束,所以查询的次数就为x,而x等于log2n(以2为底,n的对数)。即O(log2n);所以,二分查找排序比较次数为:x=log2n;二分查找插入排序耗时的操作有:比较 + 后移赋值。时间复杂度如下:
- 最好情况:查找的位置是有序区的最后一位后面一位,则无须进行后移赋值操作,其比较次数为:log2n 。即O(log2n)
- 最坏情况:查找的位置是有序区的第一个位置,则需要的比较次数为:log2n,需要的赋值操作次数为n(n-1)/2加上 (n-1) 次。即O(n^2)
- 渐进时间复杂度(平均时间复杂度):O(n^2)
- 空间复杂度:O(1)
从实现原理可知,二分查找插入排序是在原输入数组上进行后移赋值操作的(称“就地排序”),所需开辟的辅助空间跟输入数组规模无关,所以空间复杂度为:O(1)
(三)稳定性
二分查找排序是稳定的,不会改变相同元素的相对顺序。
快速排序
快速排序有两个方向,左边的i下标一直往右走,当a[i] <= a[center_index],其中center_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走,当a[j] > a[center_index]。如果i和j都走不动了,i <= j,交换a[i]和a[j],重复上面的过程,直到i > j。 交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为5 3 3 4 3 8 9 10 11,现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j] 交换的时刻。
原理:划分交换排序,使用分治法策略,把一个序列分为两个子序列。
步骤:
1.从数列中挑选一个元素,作为“基数”(pivot)
2.重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆放在基准后面(相同的数可以到任何一边)。在这个分区结束后,该基准就处于数列的中间位置,这个称为分区(partition)操作。
3.递归的把小于基准值元素的子序列和大于基准值元素的子序列排序
递归到最底部时,数列的大小是零或一,也就是已经排序好了。
function quicksort(q) var list less, pivotList, greater if lenght(q) <= 1{ return q; }else{ select a pivot value pivot from q for each x in q except the pivot element if x < pivot then add x to less if x >= pivot then add x to greater add pivot to pivotList return concatenate(quicksort(less),pivot,quicksort(greater)) }
**上述版本的缺点是,它需要
function partition(a, left, right, pivotIndex) pivotValue := a[pivotIndex] swap(a[pivotIndex], a[right]) // 把pivot移到結尾 storeIndex := left for i from left to right-1 if a[i] <= pivotValue swap(a[storeIndex], a[i]) storeIndex := storeIndex + 1 swap(a[right], a[storeIndex]) // 把pivot移到它最後的地方 return storeIndex
它分区了标示为”左边(left)”和”右边(right)”的序列部分,借由移动小于a[pivotIndex]的所有元素到子序列的开头,留下所有大于或等于的元素接在他们后面。在这个过程它也为基准元素找寻最后摆放的位置,也就是它回传的值。它暂时地把基准元素移到子序列的结尾,而不会被前述方式影响到。由于算法只使用交换,因此最后的数列与原先的数列拥有一样的元素。要注意的是,一个元素在到达它的最后位置前,可能会被交换很多次。
procedure quicksort(a, left, right) if right > left select a pivot value a[pivotIndex] pivotNewIndex := partition(a, left, right, pivotIndex) quicksort(a, left, pivotNewIndex-1) quicksort(a, pivotNewIndex+1, right)
//迭代法typedef struct _Range { int start, end;} Range;Range new_Range(int s, int e) { Range r; r.start = s; r.end = e; return r;}void swap(int *x, int *y) { int t = *x; *x = *y; *y = t;}void quick_sort(int arr[], const int len) { if (len <= 0) return; Range r[len]; int p = 0; r[p++] = new_Range(0, len - 1); while (p) { Range range = r[--p]; if (range.start >= range.end) continue; int mid = arr[range.end]; int left = range.start, right = range.end - 1; while (left < right) { while (arr[left] < mid && left < right) left++; while (arr[right] >= mid && left < right) right--; swap(&arr[left], &arr[right]); } if (arr[left] >= arr[range.end]) swap(&arr[left], &arr[range.end]); else left++; r[p++] = new_Range(range.start, left - 1); r[p++] = new_Range(left + 1, range.end); }}
//递归法void swap(int *x, int *y) { int t = *x; *x = *y; *y = t;}void quick_sort_recursive(int arr[], int start, int end) { if (start >= end) return; int mid = arr[end]; int left = start, right = end - 1; while (left < right) { while (arr[left] < mid && left < right) left++; while (arr[right] >= mid && left < right) right--; swap(&arr[left], &arr[right]); } if (arr[left] >= arr[end]) swap(&arr[left], &arr[end]); else left++; if (left) { quick_sort_recursive(arr, start, left - 1); } quick_sort_recursive(arr, left + 1, end);}void quick_sort(int arr[], int len) { quick_sort_recursive(arr, 0, len - 1);}
1.最坏时间复杂度
2. 最优时间复杂度
3. 平均时间复杂度
4. 空间复杂度 :根据实现的方式不同而不同
归并排序
归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定性。那么,在短的有序序列合并的过程中,稳定是是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。
原理:是创建在归并操作上的一种有效的排序算法,采用分治法的思想。将两个已经排序的序列合并成一个序列的操作,依赖于归并操作。
步骤:
1迭代法:
1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2.设定两个指针,最初位置分别为两个已经排序序列的起始位置
3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4.重复步骤3直到某一指针到达序列尾
5.将另一序列剩下的所有元素直接复制到合并序列尾
2递归发:
将序列每相邻两个数字进行归并操作,形成
将上述序列再次归并,形成
重复步骤2,直到所有元素排序完毕
//迭代法int min(int x, int y) { return x < y ? x : y;}void merge_sort(int arr[], int len) { int* a = arr; int* b = (int*) malloc(len * sizeof(int)); int seg, start; for (seg = 1; seg < len; seg += seg) { for (start = 0; start < len; start += seg + seg) { int low = start, mid = min(start + seg, len), high = min(start + seg + seg, len); int k = low; int start1 = low, end1 = mid; int start2 = mid, end2 = high; while (start1 < end1 && start2 < end2) b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++]; while (start1 < end1) b[k++] = a[start1++]; while (start2 < end2) b[k++] = a[start2++]; } int* temp = a; a = b; b = temp; } if (a != arr) { int i; for (i = 0; i < len; i++) b[i] = a[i]; b = a; } free(b);}
//递归法void merge_sort_recursive(int arr[], int reg[], int start, int end) { if (start >= end) return; int len = end - start, mid = (len >> 1) + start; int start1 = start, end1 = mid; int start2 = mid + 1, end2 = end; merge_sort_recursive(arr, reg, start1, end1); merge_sort_recursive(arr, reg, start2, end2); int k = start; while (start1 <= end1 && start2 <= end2) reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++]; while (start1 <= end1) reg[k++] = arr[start1++]; while (start2 <= end2) reg[k++] = arr[start2++]; for (k = start; k <= end; k++) arr[k] = reg[k];}void merge_sort(int arr[], const int len) { int reg[len]; merge_sort_recursive(arr, reg, 0, len - 1);}
1.最坏时间复杂度
2. 最优时间复杂度
3. 平均时间复杂度
4. 空间复杂度 :
桶排序
原理:将数组分到有限数量的桶里,每个桶在个别排序(有可能在使用别的排序算法或是以递归方式继续使用桶排序进行排序)。
步骤:
1.设置一个定量的数组当作空桶子。
2.寻访序列,并且把项目一个一个放到对应的桶子去。
3.对每个不是空的桶子进行排序。
4.从不是空的桶子里把项目再放回原来的序列中。
动态演示桶排序
//伪代码function bucket-sort(array, n) is buckets ← new array of n empty lists for i = 0 to (length(array)-1) do insert array[i] into buckets[msbits(array[i], k)] for i = 0 to n - 1 do next-sort(buckets[i]) return the concatenation of buckets[0], ..., buckets[n-1]
#include<iterator>#include<iostream>#include<vector>using namespace std;const int BUCKET_NUM = 10;struct ListNode{ explicit ListNode(int i=0):mData(i),mNext(NULL){} ListNode* mNext; int mData;};ListNode* insert(ListNode* head,int val){ ListNode dummyNode; ListNode *newNode = new ListNode(val); ListNode *pre,*curr; dummyNode.mNext = head; pre = &dummyNode; curr = head; while(NULL!=curr && curr->mData<=val){ pre = curr; curr = curr->mNext; } newNode->mNext = curr; pre->mNext = newNode; return dummyNode.mNext;}ListNode* Merge(ListNode *head1,ListNode *head2){ ListNode dummyNode; ListNode *dummy = &dummyNode; while(NULL!=head1 && NULL!=head2){ if(head1->mData <= head2->mData){ dummy->mNext = head1; head1 = head1->mNext; }else{ dummy->mNext = head2; head2 = head2->mNext; } dummy = dummy->mNext; } if(NULL!=head1) dummy->mNext = head1; if(NULL!=head2) dummy->mNext = head2; return dummyNode.mNext;}void BucketSort(int n,int arr[]){ vector<ListNode*> buckets(BUCKET_NUM,(ListNode*)(0)); for(int i=0;i<n;++i){ int index = arr[i]/BUCKET_NUM; ListNode *head = buckets.at(index); buckets.at(index) = insert(head,arr[i]); } ListNode *head = buckets.at(0); for(int i=1;i<BUCKET_NUM;++i){ head = Merge(head,buckets.at(i)); } for(int i=0;i<n;++i){ arr[i] = head->mData; head = head->mNext; }}
1.最坏时间复杂度
2. 最优时间复杂度
3. 平均时间复杂度
4. 空间复杂度 :
基数排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序算法。
原理:类似桶排序,这里总是需要10个桶,多次使用
步骤:首先以个位数的值进行装桶,即个位数为1则放入1号桶,为9则放入9号桶,暂时忽视十位数
例如:
待排序数组[62,14,59,88,16]简单点五个数字
分配10个桶,桶编号为0-9,以个位数数字为桶编号依次入桶,变成下边这样
| 0 | 0 | 62 | 0 | 14 | 0 | 16 | 0 | 88 | 59 |
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |桶编号
将桶里的数字顺序取出来,
输出结果:[62,14,16,88,59]
再次入桶,不过这次以十位数的数字为准,进入相应的桶,变成下边这样:
由于前边做了个位数的排序,所以当十位数相等时,个位数字是由小到大的顺序入桶的,就是说,入完桶还是有序
| 0 | 14,16 | 0 | 0 | 0 | 59 | 62 | 0 | 88 | 0 |
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |桶编号
因为没有大过100的数字,没有百位数,所以到这排序完毕,顺序取出即可
最后输出结果:[14,16,59,62,88]
int maxbit(int data[], int n) //辅助函数,求数据的最大位数{ int maxData = data[0]; ///< 最大数 /// 先求出最大数,再求其位数,这样有原先依次每个数判断其位数,稍微优化点。 for (int i = 1; i < n; ++i) { if (maxData < data[i]) maxData = data[i]; } int d = 1; int p = 10; while (maxData >= p) { //p *= 10; // Maybe overflow maxData /= 10; ++d; } return d;/* int d = 1; //保存最大的位数 int p = 10; for(int i = 0; i < n; ++i) { while(data[i] >= p) { p *= 10; ++d; } } return d;*/}void radixsort(int data[], int n) //基数排序{ int d = maxbit(data, n); int *tmp = new int[n]; int *count = new int[10]; //计数器 int i, j, k; int radix = 1; for(i = 1; i <= d; i++) //进行d次排序 { for(j = 0; j < 10; j++) count[j] = 0; //每次分配前清空计数器 for(j = 0; j < n; j++) { k = (data[j] / radix) % 10; //统计每个桶中的记录数 count[k]++; } for(j = 1; j < 10; j++) count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶 for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中 { k = (data[j] / radix) % 10; tmp[count[k] - 1] = data[j]; count[k]--; } for(j = 0; j < n; j++) //将临时数组的内容复制到data中 data[j] = tmp[j]; radix = radix * 10; } delete []tmp; delete []count;}
希尔排序
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小, 插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比O(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
原理:基于插入排序,使数组中任意间隔为h的元素都是有序的,即将全部元素分为h个区域,使用插入排序。
template<typename T>void shell_sort(T arr[], int len) { int gap, i, j; T temp; for (gap = len >> 1; gap > 0; gap >>= 1) for (i = gap; i < len; i++) { temp = arr[i]; for (j = i - gap; j >= 0 && arr[j] > temp; j -= gap) arr[j + gap] = arr[j]; arr[j + gap] = temp; }}
1.最坏时间复杂度 根据步长序列的不同而不同。已知最好的:
2.最优时间复杂度
3.平均时间复杂度 根据步长序列的不同而不同。
4.空间复杂度:
堆排序
我们知道堆的结构是节点i的孩子为2 * i和2 * i + 1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n / 2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n / 2 - 1, n / 2 - 2, … 1这些个父节点选择元素时,就会破坏稳定性。有可能第n / 2个父节点交换把后面一个元素交换过去了,而第n / 2 - 1个父节点把后面一个相同的元素没 有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。
原理:利用堆这种数据结构所设计的排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质,即子节点的键值或索引总是小于(或者大于)它的父节点。
通常堆是通过一维数组来实现的。在数组起始位置为0的情形中:
父节点i的左子节点在位置(2*i+1);
父节点i的右子节点在位置(2*i+2);
子节点i的父节点在位置floor((i-1)/2);
在堆的数据结构中,堆中的最大值总是位于根节点(在优先队列中使用堆的话堆中的最小值位于根节点)。堆中定义以下几种操作:
最大堆调整(Max_Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
创建最大堆(Build_Max_Heap):将堆所有数据重新排序
堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算
#include <stdio.h>#include <stdlib.h>void swap(int* a, int* b) { int temp = *b; *b = *a; *a = temp;}void max_heapify(int arr[], int start, int end) { //建立父節點指標和子節點指標 int dad = start; int son = dad * 2 + 1; while (son <= end) { //若子節點指標在範圍內才做比較 if (son + 1 <= end && arr[son] < arr[son + 1]) //先比較兩個子節點大小,選擇最大的 son++; if (arr[dad] > arr[son]) //如果父節點大於子節點代表調整完畢,直接跳出函數 return; else { //否則交換父子內容再繼續子節點和孫節點比較 swap(&arr[dad], &arr[son]); dad = son; son = dad * 2 + 1; } }}void heap_sort(int arr[], int len) { int i; //初始化,i從最後一個父節點開始調整 for (i = len / 2 - 1; i >= 0; i--) max_heapify(arr, i, len - 1); //先將第一個元素和已排好元素前一位做交換,再從新調整,直到排序完畢 for (i = len - 1; i > 0; i--) { swap(&arr[0], &arr[i]); max_heapify(arr, 0, i - 1); }}int main() { int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 }; int len = (int) sizeof(arr) / sizeof(*arr); heap_sort(arr, len); int i; for (i = 0; i < len; i++) printf("%d ", arr[i]); printf("\n"); return 0;}
1.最坏时间复杂度
2.最优时间复杂度
3.平均时间复杂度
4.空间复杂度
计数排序
原理 使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数,然后根据数组c来将A中的元素排到正确的位置。
步骤
1.找出待排序的数组中最大和最小的元素
2.统计数组中每个值为i的元素出现的次数,存入数组 C 的第 i 项
3.对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
4.反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
#include <stdio.h>#include <stdlib.h>#include <time.h>void print_arr(int *arr, int n) { int i; printf("%d", arr[0]); for (i = 1; i < n; i++) printf(" %d", arr[i]); printf("\n");}void counting_sort(int *ini_arr, int *sorted_arr, int n) { int *count_arr = (int *) malloc(sizeof(int) * 100); int i, j, k; for (k = 0; k < 100; k++) count_arr[k] = 0; for (i = 0; i < n; i++) count_arr[ini_arr[i]]++; for (k = 1; k < 100; k++) count_arr[k] += count_arr[k - 1]; for (j = n; j > 0; j--) sorted_arr[--count_arr[ini_arr[j - 1]]] = ini_arr[j - 1]; free(count_arr);}int main(int argc, char **argv) { int n = 10; int i; int *arr = (int *) malloc(sizeof(int) * n); int *sorted_arr = (int *) malloc(sizeof(int) * n); srand(time(0)); for (i = 0; i < n; i++) arr[i] = rand() % 100; printf("ini_array: "); print_arr(arr, n); counting_sort(arr, sorted_arr, n); printf("sorted_array: "); print_arr(sorted_arr, n); free(arr); free(sorted_arr); return 0;}
快速排序是二叉查找树(二叉查找树)的一个空间最优化版本。不是循序地把数据项插入到一个明确的
树中,而是由快速排序组织这些数据项到一个由递归调用所隐含的树中。这两个算法完全地产生相同的比较次数,但是顺序不同。对于排序算法的稳定性指标,原地分区版本的快速排序算法是不稳定的。其他变种是可以通过牺牲性能和空间来维护稳定性的。
快速排序的最直接竞争者是堆排序(Heapsort)。堆排序通常比快速排序稍微慢,但是最坏情况的运行时间总是O(n log n)。快速排序是经常比较快,除了introsort变化版本外,仍然有最坏情况性能的机会。如果事先知道堆排序将会是需要使用的,那么直接地使用堆排序比等待introsort再切换到它还要快。堆排序也拥有重要的特点,仅使用固定额外的空间(堆排序是原地排序),而即使是最佳的快速排序变化版本也需要Θ(log n)的空间。然而,堆排序需要有效率的随机存取才能变成可行。
快速排序也与归并排序(Mergesort)竞争,这是另外一种递归排序算法,但有坏情况O(n log n)运行时间的优势。不像快速排序或堆排序,归并排序是一个稳定排序,且可以轻易地被采用在链表(linked list)和存储在慢速访问媒体上像是磁盘存储或网络连接存储的非常巨大数列。尽管快速排序可以被重新改写使用在炼串列上,但是它通常会因为无法随机存取而导致差的基准选择。归并排序的主要缺点,是在最佳情况下需要Ω(n)额外的空间。
快速排序:https://zh.wikipedia.org/wiki/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F
冒泡排序:https://zh.wikipedia.org/wiki/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F
选择排序:
http://www.cnblogs.com/QG-whz/p/5194569.html
http://blog.sina.com.cn/s/blog_eb52001d0102v1k8.html
https://zh.wikipedia.org/wiki/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F
http://www.cnblogs.com/kkun/archive/2011/11/23/2260275.html
https://zh.wikipedia.org/wiki/%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F
- 基础排序算法总结
- 基础排序算法总结
- 基础排序算法总结
- 基础排序算法总结
- 基础排序算法总结
- 基础算法总结之堆排序算法
- 基础排序算法总结(七种排序算法)
- 基础算法的总结之快速排序
- 基础算法总结之归并排序
- [数据结构]九大基础排序算法总结
- 基础知识点总结:查找和排序算法
- 基础排序算法总结及实现
- 排序基础算法总结与c++实现
- 算法基础:基本排序算法原理、实现与总结
- 算法--排序算法总结
- 算法:排序算法总结
- 算法:排序算法总结
- 算法-排序算法总结
- Java的继承
- 苹果内购返回错误代码描述
- Eclipse注释模板设置详解
- SpringBoot之LogBack常用配置
- Python 中的 if __name__ == '__main__' 该如何理解
- 基础排序算法总结
- window 安装 scrapy 框架
- 小C语言--词法分析程序
- static函数、变量
- 正阅读微信小说分销系统-视频教程-6.生成推广文章-代理商必看-没有公众号也能推哦!
- 工作开发帮助博客收藏
- Maximum Binary Tree
- DB2 10.5 linux 数据库查询命令 及 表空间降低高水位(释放磁盘空间)
- 关于python无法显示中文的问题:SyntaxError: Non-ASCII character '\xe4' in file test.py on line 3, but no encoding