排序算法
来源:互联网 发布:淘宝9月什么时候有活动 编辑:程序博客网 时间:2024/06/05 09:26
1. 直接选择排序
任何情况下的时间复杂度都是 O(n^2), 最差的一种排序,每次都必须遍历玩整个子数组,选出最小的那个,空间复杂度为O(1),无需额外空间。直接选择排序的关键在于,每次从右侧子数组中,选出最小的,放到子数组第一个,然后缩小子数组范围,再次选择、交换。
#include <iostream>using namespace std;void selectionSort(int* a, int begin, int end){ if (begin >= end) return; int min = a[begin]; for (int i = begin; i < end; i++) { if (a[i] < min) { // 不使用第三个数交换两个整型。 a[i] = a[i] ^ a[begin]; a[begin] = a[i] ^ a[begin]; a[i] = a[i] ^ a[begin]; min = a[begin]; } } selectionSort(a, ++begin, end);}int main(){ int n[7] = { 10, 10, 20, 1, 3, 8, 22 }; selectionSort(n, 0, 7);}
2. 直接插入排序
直接插入排序的平均复杂度为O(n^2),在最好的情况下,即基本有序时,复杂度为O(n).直接插入排序的关键在于,与直接选择相反,直接插入的操作序列是左侧的已排好序列,每次把右侧第一个值拿出来插到左侧已排好序列中。
int* insertionSort(int* A, int n) { for(int i = 1; i < n; i++) for(int j = i; j > 0; j--) if(A[j] < A[j - 1]) swap(A[j], A[j - 1]); return A; }
3. 希尔排序
希尔排序是直接插入排序的改良版,gap不再是1,而是动态变化,从n/2开始,每次除以2,直到1。利用了插入排序在基本有序的前提下效率最高的特点。
void shell_sort(int a[], int n){ for (int gap = n / 2; gap > 0; gap /= 2) // gap 从 n / 2 .. 1 { // 下面是插入排序 for (int i = gap; i < n; i++) // 遍历一遍 {// 步长不再是直接插入时的1,而是gap // 前gap个([0] .. [gap - 1])是不需要检测的,因为是每组的第一个,所以i从gap开始 for (int j = i; j >= gap; j -= gap) // 注意这里 j-=gap { if (a[j] < a[j - gap]) swap(a[j - gap], a[j]); else break; } } }}
4. 直接交换(冒泡排序)
冒泡排序的平均时间复杂度为O(n^2),在最好情况下,即基本有序时,复杂度为O(n)。冒泡排序的关键是,每次冒泡上来一个当前子序列中的最大值(升序排列的话)。冒泡排序可以优化,比如下面,用exchange标记,如果某次冒泡没有交换,说明当时的子序列已经有序。
#include <iostream>using namespace std;void exchangeSort(int* n, int s){ // 优化冒泡排序 bool isexchange = false; for (int i = 0; i < s; i++) { isexchange = false; for(int j = 0; j < s - i - 1; j++) { if (n[j + 1] < n[j]) { isexchange = true; n[j + 1] ^= n[j]; n[j] ^= n[j + 1]; n[j + 1] ^= n[j]; } } if (isexchange == false) return; }}int main(){ int n[7] = { 10, 10, 20, 1, 3, 8, 22 }; exchangeSort(n, sizeof(n)/ sizeof(int)); }
5.快速排序(加强版交换排序)
关键字:pivot 轴,partition() (划分)
快排有两种实现,分别应用于数组和单向链表,快排的关键是partition函数的实现 http://www.cnblogs.com/TenosDoIt/p/3665038.html
1. 定轴实现
对数组进行快排时使用这个方法。
快排采用了分治策略,平均时间复杂度为O(nlogn)。快排的关键在于选择基准,把子序列从左右两端,(如果是升序的话)左边找比基准大的,右边找比基准小的,交换两者,直到左右指针相遇。一个while嵌套两个查找while。快排在最坏情况下(逆序),复杂度为O(n^2)partition 函数:
// 划分函数,返回pivot的最终位置int partition(int a[], int begin, int end){ // 选第一个作pivot即可 int pivot = a[begin]; int l = begin, r = end; while (l < r) { // 从右往左找小的 while (l < r && a[r] >= pivot) --r; a[l] = a[r];// 找到后把它放到低位,a[l]上肯定要么是a[begin]要么是大于pivot的,所以可以覆盖(提示:考虑下面这个循环退出的条件) while (l < r && a[l] <= pivot) ++l; a[r] = a[l];// 找到后把它放到高位,a[r]上肯定是小于pivot的,可以覆盖 } // 此时 l 应该等于 r,这个是一个空位,可以覆盖,把pivot放到这里,左边的小于pivot,右边的大于pivot a[l] = pivot; return l;}
快排的另一种实现是挖坑实现
2. 挖坑法
对链表进行快排时使用这个方法
// 划分函数,返回pivot的最终位置int partition(int a[], int begin, int end){ int pivot = a[begin]; //选第一个作pivot int low = begin; // 比pivot小的最后一个元素的下标 for (int i = begin + 1; i <= end; i++) // 比pivot小的放在左边 if (a[i] < pivot) swap(a[i], a[++low]);//将此数与比pivot小的最后一个元素的下一个(比pivot大)交换 swap(a[begin], a[low]); // 将最后一个比pivot小的元素交换到头部 return low;}两种实现的 quicksort入口都是一样的,
void quicksort(int a[], int begin, int end){ if (begin < end) { int pivot = partition(a, begin, end); // 使得总体有序,找到pivot的最终位置, quicksort(a, begin, pivot - 1); // 使得左侧有序 quicksort(a, pivot + 1, end); // 使得右侧有序 }}
6. 归并排序
关键字:merge,() mergesort()
归并排序有两个函数,一个是用来合并两个已经排好的数组,并放到临时数组里,比较容易
// 将 begin .. end 排好序后放回a数组的 begin .. end 原位置// a[]的begin .. mid 与 mid + 1 .. end 子数组分别是两个已经排好序的数组// 需要借用一个辅助存储空间 temp, 这也是归并排序空间复杂度为O(n)的原因所在void merge(int a[], int begin, int mid, int end, int temp[]){ int l = begin, r = mid + 1; int k = 0; while (l <= mid && r <= end) // 当两个子数组都没用完时 temp[k++] = (a[l] < a[r] ? a[l++] : a[r++]); while (l <= mid) // 当第一个没有用完时,用完它 temp[k++] = a[l++]; while (r <= end) // 当第二个没有用完时,用完它 temp[k++] = a[r++]; // 将temp中的内容拷贝回 a[]的begin, end位置 for (int i = begin; i <= end; i++) a[i] = temp[i - begin]; }然后是主调函数,是一个拆分函数
// 主调函数void mergesort(int a[], int begin, int end, int temp[]){ if (begin < end) { int mid = (begin + end) >> 1; // 取中点 mergesort(a, begin, mid, temp); // 另左边有序,临时数组temp可以多次使用 mergesort(a, mid + 1, end, temp); // 另右边有序 merge(a, begin, mid, end, temp); // 合并左右两个有序子数组 }}用的时候,传进来 数组名,0, len - 1, 和一个临时数组即可
快排与归并排序的对比
1. 快排和归并排序都应用了分治的思想。快排是交换排序的一种。
2. 快排最坏复杂度为 O(n^2), 与pivot选取有关,会退化为冒泡排序,归并排序最坏也是O(n^2)
3. 归并排序需要另外的O(n)存储空间;快排不需要,直接对原数组操作。
4. 快排每次保证整体有序,即pivot左侧一定都小于pivot,pivot右侧一定都大于pivot,然后分别处理左侧和右侧,使之整体有序,直到只剩一个元素;
归并排序每次保证局部有序,划分到最底部,然后从低开始向上merge合并,从而使整体有序。
5. 归并排序稳定,快排不稳定。
7. 堆排序
堆排序是一种选择排序,关键是下调算法。堆排序的第一步是将近似堆(原数组)转化为最大堆(从最后一个非叶节点开始调用下调算法),然后每次删除根(将根与最后一个节点交换),再使用下调,调整为新的堆,再删除根。。。直到子堆大小为1。
// 下调算法// begin 代表了子堆根,从此开始下调// end 代表了子堆最后一个节点void heap_adjust(int a[], int begin, int end){ for (int i = begin * 2 + 1; i <= end; i = i * 2 + 1) { int next = i + 1; if (next <= end && a[next] > a[i]) i++; // 如果自己的兄弟比自己大,则应该让兄弟和爸爸交换 if (a[begin] > a[i]) break; // 已经满足了堆次序性 swap(a[begin], a[i]); begin = i; // 以此节点为根继续下调 }}// 先得把无序数组建立为一个大根堆(升序的话)// 使用下调建堆(从最后一个非叶节点开始,重复利用下调),这样就可以// 把建堆和调堆两个过程用一个函数 heap_adjust 进行void heap_sort(int a[], int n){ // 先用建堆算法,建立最大堆,从最后一个非叶节点开始调用下调算法 for (int i = n - 1; i >= 0; i--) heap_adjust(a, i, n - 1); // 每次把堆根和最后一个元素交换,然后再用下调算法调堆 for (int i = 0; i < n; i++) { swap(a[n - i - 1], a[0]); heap_adjust(a, 0, n - i - 2); }}
8. 桶排序
桶排序非常快,但是空间消耗大。
8.1 计数排序 counting sort
基本思路是用一个数组记录每个值出现的次数,数组的下标才是值,数组的值是次数,输出的时候从0开始到max,有几个输出几个。
#include <iostream>#include <algorithm>using namespace std;void countingsort(int a[], int n){ if (n == 0) return; // 1. 先找最大值 int max = a[0]; for (int i = 0; i < n; i++) max = (max < a[i] ? a[i]: max); // 2. 做桶 int * bucket = new int[max + 1]; memset(bucket, 0, sizeof(int)*(max + 1)); for (int i = 0; i < n; i++) bucket[a[i]] ++; int k = 0; for (int i = 0; i < max + 1; i++) { int n = bucket[i]; while (n-- > 0) a[k++] = i; } delete[] bucket; }int main(){ int a[] = { 1,7,3,5,2,4,6 }; const int len = sizeof(a) / sizeof(int); countingsort(a, len); for (int i = 0; i < len; i++) cout << a[i] << endl;}
8.2 基数排序是从各位开始装桶。
阅读全文
0 0
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 342.Power of Four
- [python]如何生成城市诺力图
- 344.Reverse String
- 345.Reverse Vowels of a String
- 35. Search Insert Position
- 排序算法
- 367. Valid Perfect Square
- 383. Ransom Note
- 387.First Unique Character in a String
- 389.Find the Difference
- 412. Fizz Buzz
- spring EnableScheduling标签使用详解
- 巡视整改不能走过场,更不能搞“一风吹”
- Spring04---Annotation注入