排序算法
来源:互联网 发布:6sigma软件 编辑:程序博客网 时间:2024/06/05 23:47
基础知识介绍
1、稳定排序和非稳定排序
简单地说就是所有相等的数经过某种排序方法后,仍能保持它们在排序之前的相对次序,我们就说这种排序方法是稳定的。反之,就是非稳定的。比如:一组数排序前是a1,a2,a3,a4,a5,其中a2=a4,经过某种排序后为a1,a2,a4,a3,a5,则我们说这种排序是稳定的,因为a2排序前在a4的前面,排序后它还是在a4的前面。假如变成a1,a4,a2,a3,a5就不是稳定的了。
2、内排序和外排序
在排序过程中,所有需要排序的数都在内存,并在内存中调整它们的存储顺序,称为内排序;
在排序过程中,只有部分数被调入内存,并借助内存调整数在外存中的存放顺序排序方法称为外排序。
3、算法的时间复杂度和空间复杂度
所谓算法的时间复杂度,是指执行算法所需要的计算工作量。
一个算法的空间复杂度,一般是指执行这个算法所需要的内存空间。
插入排序
1、直接插入排序(Straight Insertion Sort)
思想:整个记录分为有序区和无序区,依次从无序区取一个记录插入到有序区。下标为0的记录设置为哨兵,用作比较。
核心代码:
for(int i = 2; i <= n; i++){ iArray[0] = iArray[i]; int j; for(j = i - 1; iArray[j] > iArray[0]; j--){iArray[j+1] = iArray[j]; } iArray[j+1] = iArray[0]; }时间复杂度:
(1)最好情况:整个序列为正序,总的比较次数是n-1,移动次数是2(n-1),因此时间复杂度是O(n);
(2)最坏情况:真个序列为逆序,在第i趟插入时,i需与临界条件以及前面i-1个记录做比较,因此总的比较次数是2+.....+n,总的比较次数是从(n+2)*(n - 1)/2,同时移动次数是3+......+(n+1),总的移动次数是(n+4)*(n-1)/2,因此时间复杂度是O(n^2)。
(3)平均情况下,各种排列的概率相同,在插入第i个记录时平均所需比较的记录时序列的一半,所以总的比较次数是(n*2)(n-1)/4,移动次数是(n+4)(n-1)/4,因此,时间复杂度是O(n^2)。
空间复杂度:O(1)。
直接插入排序是一种稳定的排序方法。
2、希尔排序(Shell Sort)
思想:它是对直接插入排序的一种改进,因为当序列基本有序或者记录数较少时,直接插入排序的效率很高。因此希尔排序是将序列分成若干子序列,在子序列内分别进行排序,待整个序列基本有序时,再对整个序列进行直接插入排序。
核心代码:
int d, i, j;for(d = n / 2; d >= 1; d = d / 2){ //操作与插入排序类似 for(i = d + 1; i <= n; i++){iArray[0] = iArray[i];for(j = i -d; j >= 1 && iArray[j] > iArray[0]; j -= d){ iArray[j+d] = iArray[j];}iArray[j+d] = iArray[0]; }}时间复杂度:分析复杂,据前人大量的实验结果得出,时间性能在O(n^2)和O(nlog2(n))之间。当n在某个特定范围时,时间性能约为O(n^1.3)。
空间复杂度:O(1)。
希尔排序是一种不稳定的排序方法。
交换排序
1、冒泡排序(Bubble Sort)
思想:序列分为有序区和无序区,在无序区将记录两两比较,如果反序则交换记录,选出的最大记录放在有序区,有序区记录加1,无序区记录减一,循环此过程,直到无序区没有反序为止。
核心代码:
int i, j, exchagne;exchange = n;while(exchange != 0){ int flag = exchange; exchangne = 0; //比较区间为[1,flag] for(j = 1; j < flag; j++){if(iArray[j] > iArray[j+1]){ iArray[0] = iArray[j]; iArray[j] = iArray[j+1]; iArray[j+1] = iArray[0]; //记录上一趟排序最后的交换位置,避免无用比较 exchange = j; } }}时间复杂度:
(1)最好情况下,整个序列为正序,算法只执行一趟,比较了n-1次,不移动记录。时间复杂度是O(n)。
(2)最坏情况下,整个序列是逆序,总的比较次数是(n-1)+.....+1,因此时间复杂度是(n-1)*n/2,移动次数3*n*(n-1)/2,因此总的时间复杂度是O(n^2)。
(3)平均情况下,时间复杂度与最坏程度同一等级,O(n^2)。
空间复杂度:O(1)。
冒泡排序是一种稳定的排序算法。
2、快速排序(Quick Sort)
思想:冒泡排序记录的比较和移动是在相邻位置进行的,记录每次只能后移一位,因此总的比较次数和移动次数较多。快速排序,比较和移动是从两端向中间进行的,记录的移动距离较远,减少了总的比较次数和移动次数。它是通过选择一个中间轴值,将序列分为两个独立的部分,左侧均小于或等于轴值,右侧均大于或等于轴值。 核心代码:
int getLocate(int *iArray, int first, int end){ int i = first, j = end; while(i < j){ //循环保证i指针永远小于j指针 while(i<j && iArray[i] <= iArray[j]){ j--;}if(i < j){ iArray[0] = iArray[i]; iArray[i] = iArray[j]; iArray[j] = iArray[0]; i++;}while(i<j && iArray[i] <= iArray[j]){ i++;}if(i < j){ iArray[0] = iArray[i]; iArray[i] = iArray[j]; iArray[j] = iArray[0]; j--;} } return i;}void quickSort(int *iArray, int first, int end){ if(first < end){ //获取轴值 int location = getLocate(iArray, first, end); //递归对每个子序列进行交换排序 quickSort(iArray, first, location-1);quickSort(iArray, location+1, end); }}时间复杂度:
(1)最好情况:O(nlog2(n))。
(2)最换情况:O(n^2)。
(3)平均情况:O(nlog2(n))。
空间复杂度:递归使用栈,最好情况下栈的深度为O(log2(n)),最坏情况下为O(n),平均情况下为O(log2(n))。
快速排序时一种不稳定的排序方法。是目前公认的一种比较好的排序算法,是qsort的内部实现。
选择排序
1、简单选择排序
思想:将序列分为有序区和无序区,在无序区中依次比较,记录最大记录的下标,比较完毕后,将最大记录放入有序区,依次循环。
核心代码:
int i, j, index;for(i = 1; i <= n; i++){ //index记录最小记录的下标 index = i; for(j = i + 1; j <= n; j++){if(iArray[j] < iArray[index]){ index = j;} } if(index != i){iArray[0] = iArray[i];iArray[i] = iArray[index];iArray[index] = iArray[0]; }}时间复杂度:无论序列如何排序,关键码的比较次数都一样,均为(n -1)*n/2,即O(n^2)。这是简单选择排序的最差、最好、平均时间性能。
空间复杂度:O(1)。
简单选择排序是一种不稳定的排序方法。
2、堆排序(Heap Sort)
思想:简单选择排序每趟排序都是选出最小记录,没有将之前的比较结果保存起来,因此比较次数较多。堆排序是在选择最小关键码的同时,记录较小关键码。堆排序首先将序列构造成一个堆,此时,选出最大的记录,即堆顶。然后移走堆顶,再将堆调整为大根堆,进行循环,直到堆中只有一个记录为止。
核心代码:
void adjustHeap(int *iArray, int locate, int n){ int i = locate, j = 2 * locate; while(j <= n){if(j < n && iArray[j] < iArray[j+1]){ j++;}if(iArray[i] < iArray[j]){ iArray[0] = iArray[j]; iArray[j] = iArray[i]; iArray[i] = iArray[0]; i = j; j = 2 * i;}else{ break;} }}void heapSort(int *iArray, int n){ int i, j; for(i = n /2; i >= 1; i--){adjustHeap(iArray, i, n); } for(j = n; j > 1; j--){iArray[0] = iArray[j];iArray[j] = iArray[1];iArray[1] = iArray[0];adjustHeap(iArray, 1, j-1); }}时间复杂度:初建堆需要O(n)时间,第i次取堆顶记录重建堆需要O(log2(i))时间,并且需要取n-1次堆顶记录。因此总的时间复杂度为O(nlog2(n)),这是堆排序的最好、最差、平均情况。
空间复杂度:O(1)。
堆排序时一种不稳定的排序方法。
归并排序
1、二路归并排序算法(非递归算法)
思想:将若干有序序列两两归并,直至所有待排序记录都在一个有序序列为止。
核心代码:
//一次归并void merge(int *iArray, int *iArray1, int startLocate, int endLocate1, int endLocate2){ int k = startLocate; int i = k, j = endLocate1 + 1; int m1 = endLocate1, m2 = endLocate2; //m1:子序列1的结束位置 m2:子序列2的结束位置 while(i <= m1 && j <= m2){if(iArray[i] <= iArray[j]){ iArray1[k++] = iArray[i++]; }else{ iArray1[k++] = iArray[j++];} } if(i <= m1){while(i <= m1){ iArray1[k++] = iArray[i++];} }else{while(j <= m2){ iArray1[k++] = iArray[j++];} }}/*归并公共存在三种关系:1、当i<n-2*h+1时,存在两个长度相同的子序列,可以进行归并操作2、当i<n-h+1时,存在两个子序列,当其中一个子序列长度不足h,可以进行归并操作。3、当i>=n-h+1时,只有一个子序列。直接复制到数组末尾。*/void mergePass(int *iArray, int *iArray1, int distance, int len){ int i = 1; int n = len; int h = distance; while(i <= n-2*h+1){merge(iArray, iArray1, i, i+h-1, i+2*h-1);i += 2*h; } if(i < n-h+1){merge(iArray, iArray1, i, i+h-1, n); }else{for(int k = i; k <= n; k++){ iArray1[k] = iArray[k];} }}//定义一个长度为n的数组用来存放归并结果void mergeSort(int *iArray, int *iArray1, int n){ int h = 1; //保证成对操作,防止当归并趟数为奇数时,结果保存在iArray1中 while(h < n){mergePass(iArray, iArray1, h, n);h = 2*h; // 查看h值 cout<<h<<endl; mergePass(iArray1, iArray, h, n); h = 2*h; // 查看h值 cout<<h<<endl; }}时间复杂度:一趟归并所需时间为n,整个归并排序时间复杂度是O(log2(n))。因此,总的时间复杂度是O(nlog2(n)),这是归并排序算法最好、最坏、平均的时间性能。
空间复杂度:O(n)。
二路归并时一种稳定的排序方法。
排序方法比较
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- hdu4930Fighting the Landlords(直接枚举)
- 暑期个人赛--第九场--C(字符串 朴素查找)
- golang--- 32位系统下atomic.AddUint64导致程序崩溃及解决办法
- poj3252Round Numbers
- UIViewController的切换方式
- 排序算法
- gdfsgdskgjkdlf
- 什么是java序列化,如何实现java序列化?
- sqlserver中newid()和newsequentialid()的区别
- 使用Code Snippet在Xcode中添加代码段
- 每天一个linux命令:wget命令
- 关于在退出应用时关闭所有Activity
- B-number
- ADB工具的安装与卸载命令的实践!!!