这里的排序方法是发生在内存中,因此是内部排序。
1.插入排序-直接插入排序
基本思想:将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。
稳定性:如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
复杂度:平均复杂度:O(n^2)。
最优复杂度:当输入数组就是排好序的时候,复杂度为O(n)。
最差复杂度:当输入数组为倒序时,复杂度为O(n^2)。
因此插入排序比较适合“少量元素的数组”。
#define _CRT_SECURE_NO_DEPRECATE #include<stdio.h> #include<stdlib.h> #define MAXSIZE 20 void InsertSort(int s[], int n)//插入排序,稳定O(n^2) { int j,temp; for (int i = 1; i < n - 1; i++) { temp = s[i]; //将此元素temp与前面有序状态的序列往前依次比较,temp小,则将大的元素往后移动,知道找到合适位置 for (j = i - 1; j >= 0 && s[j]>temp; j--) s[j + 1] = s[j]; s[j + 1] = temp;//正确位置j+1 } }
2.插入排序-希尔排序
基本思想:先将整个待排序的记录序列利用跨度dk分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
稳定性:不稳定。
复杂度:平均复杂度O(n^1.3)(课本要求平均、最优、最差都为(n^1.3))。
//希尔排序就是插入排序的改进,只不过将插入排序的每次比较幅度1变成了dk(dk也是最终变化到1的) void ShellSort(int s[], int n)//希尔排序,不稳定 O(n^3/2) { int i,j, dk, temp; dk = n / 2; while (dk >= 1) { for (i = dk ;i < n ;i++) { temp = s[i]; for (j = i - dk; j >= 0 && temp < s[j]; j -= dk) s[j + dk] = s[j]; s[j + dk] = temp; } dk /= 2; } }
3.交换排序-冒泡排序
基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
稳定性:稳定。
时间复杂度:平均复杂度:O(n^2)。
最优复杂度:当输入数组就是排好序的时候,复杂度为O(n)。
最差复杂度:当输入数组为倒序时,复杂度为O(n^2)。
void BubbleSort(int s[],int n)//冒泡排序,稳定O(n^2) { bool change;//改进的冒泡排序,添加bool型变量,当某一趟检测已经有序则结束 int temp; int count1=0, count2=0;//计数变量 for (int i = 1; i < n; i++)//双循环来进行冒泡排序 { change = false;//此趟开始初始化change变量 for (int j = 0; j < n - i; j++) { count1++; if (s[j]>s[j + 1]) { count2++; temp = s[j]; s[j] = s[j + 1]; s[j + 1] = temp; change = true;//有交换了,此时还未成有序状态,设置change变量 } } if (change == false)//如果此趟结束后还未有过交换,即已是有序状态 break; } printf("总共比较%3d次,总共交换%3d次\n", count1, count2); }
4.交换排序-快速排序
基本思想:
1)选择一个基准元素,通常选择第一个元素或者最后一个元素,
2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。
3)此时基准元素在其排好序后的正确位置
4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。
稳定性:不稳定。
时间复杂度:平均复杂度:O(nlogn)。
最优复杂度:当输入数组就是排好序的时候,复杂度为O(nlogn)。
最差复杂度:当输入数组为倒序时,复杂度为O(n^2)。
辅助空间:O(nlogn)。
void Quick(int s[], int low, int high) { int point,i,j; if (low < high) { point = s[low];//以第一个元素来找其应该所在的位置 i = low ; j = high; while (i < j) { while (i < j&&s[j] >= point)//右边小的放左边 j--; s[i] = s[j]; while (i<j&&s[i]<=point)//左边大的放右边 i++; s[j] = s[i]; } s[i] = point; Quick(s, low, i - 1);//递归左边 Quick(s, i + 1, high);//递归右边 } }
为改进之,通常以“三者取中法”来选取基准记录,即将排序区间的两个端点与中点三个记录关键码大小顺序居中的调整为支点记录,当规模小时采用直接插入排序。
void swap(int s[], int low,int high)//交换函数 { int t; t = s[low]; s[low] = s[high]; s[high] = t; } void Quick1(int s[], int low, int high)//优化,选取三数取中法避免效率最低的情况 { int point, i, j; int m = (low + high) / 2; if (s[low] > s[high]) { swap(s, low, high); } if (s[m] > s[high]) { swap(s, m, high); } if (s[m] > s[low]) { swap(s, m, low); } if (high-low>6)//大规模就用快速排序 { point = s[low]; i = low; j = high; while (i < j) { while (i < j&&s[j] >= point) j--; s[i] = s[j]; while (i<j&&s[i] <= point) i++; s[j] = s[i]; } s[i] = point; Quick(s, low, i - 1); Quick(s, i + 1, high); } else { InsertSort(s+low, high-low+1);//规模小数组调用插入排序 } } void QuickSort(int s[], int n)//冒泡排序升级版 不稳定O(nlogn) { Quick1(s, 0, n - 1); }
5.选择排序-简单选择排序
基本思想:在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
稳定性:不稳定。
复杂度:平均、最优、最差复杂度都为O(n^2)。
void SelectSort(int s[], int n)//选择排序,不稳定O(n^2) { int count1 = 0, count2 = 0; int t,temp; for (int i = 0; i < n-1; i++) { t = i; for (int j = i + 1; j < n; j++)//通过比较用 t 记录最小元素的编号 { count1++; if (s[t] > s[j]) t = j; } //将此趟选出的最小元素和其在有序状态下应该处在的位置i的元素交换 temp = s[t]; s[t] = s[i]; s[i] = temp; count2++; } printf("总共比较%3d次,总共交换%3d次\n", count1, count2); }
6.选择排序-堆排序
基本思想:根据堆的定义,最大堆堆顶为最大值,堆顶元素与最后一个元素交换,和选择排序类似,然后将之前n-1个元素再继续构造堆,这样来排序,为了简单起见,利用二叉树根节点与子节点的关系,从数组编号为1开始排序。
稳定性:不稳定。
复杂度:平均、最优、最差复杂度都为O(nlogn)。
void HeapAdjust(int s[], int a, int n) { int i,temp; temp = s[a]; for (i = a * 2; i <= n; i *= 2)//一直找后代,知道符合堆原则,不能只是往下比一层 { if (i < n&& s[i] < s[i + 1])//如果有右孩子且右孩子比左孩子大,则用根元素和右孩子比 i++; if (temp >= s[i])//因为是从底向上来调整堆,当某一层符合了,下面一定符合堆的原则 break; s[a] = s[i];//最后确定的temp的位置 a = i; } s[a] = temp;//一直遍历找到适合的位置 } void HeapSort(int s[], int n)//堆排序,不稳定 { int i = 0; for (i = n / 2; i >= 1; i--)//先找最左下的度不为0的节点也就是编号为n/2(向下取整) { HeapAdjust(s, i, n); } for (i = n; i > 1; i--)//将调整后的最大堆的堆顶与最后一个元素交换,类似选择排序的思想 { swap(s, 1, i); HeapAdjust(s, 1,i-1); } }
7.归并排序
基本思想:归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
稳定性:稳定。
复杂度:平均、最优、最差复杂度都为O(nlogn)。
辅助空间:O(n)。
给出自顶向下和自底向上两种算法:
void Merging(int *list1, int list1_size, int *list2, int list2_size) {//此函数就是经典的两个有序数组合并为一个有序数组 int i, j, k; int temp[MAXSIZE]; i = j = k = 0; while (i < list1_size && j < list2_size) { if (list1[i] < list2[j]) temp[k++] = list1[i++]; else temp[k++] = list2[j++]; } while (i < list1_size) temp[k++] = list1[i++]; while (j < list2_size) temp[k++] = list2[j++]; for (int m = 0; m < (list1_size + list2_size); m++)//两个数组合并后放到list1中 list1[m] = temp[m]; } void MergeSortTop(int s[], int n)//二路归并排序递归,自上而下,稳定,O(nlogn),需要空间O(n) { if (n > 1){ int *list1 = s; int list1_size = n / 2; int *list2 = s + n / 2; int list2_size = n - list1_size; //一分二,二分四,直到分到大小1为止也就是自上而下 MergeSortTop(list1, list1_size);//左归并 MergeSortTop(list2, list2_size);//右归并 Merging(list1, list1_size, list2, list2_size);//已成的两个有序的左右合并 } } void MergePart(int *s, int n, int length)//以间隔为length的步长来分别归并 { int i = 0;//从编号为0的数组元素开始排序 for (; i + 2 * length<n - 1; i += 2 * length) { Merging(s+i, length,s+i + length, length); } if (i + length <= n - 1) Merging(s+i, length,s+i+length,n-i-length );//尚有两个子文件,其中后一个长度小于length,归并最后两个子文件 注意:若i≤n-1且i+length-1≥n-1时,则剩余一个子文件轮空,无须归并 } void MergeSortBottom(int s[], int n)//二路归并迭代,自下而上 { for (int length = 1; length<n; length *= 2) MergePart(s, n, length); }
8.测试函数:
int main() { int s[] = { -1,2, 4, 5, 3, 7, 8, 0, 6, 10, 9, 1 }; printf("排序初始序列为(有的排序方法从编号为1的元素开始,则去掉首元素):\n"); for (int i = 0; i < sizeof(s) / 4; i++) printf("%3d", s[i]); printf("\n请输入想要进行的排序:\n1代表冒泡排序\n2代表选择排序\n3代表插入排序\n4代表希尔排序\n5代表归并排序自顶而下\n6代表归并排序自下而上\n7代表快速排序\n8代表堆排序\n"); int choice = 0; scanf("%d", &choice); switch (choice) { case 1: BubbleSort(s, sizeof(s) / 4); printf("冒泡排序结果:\n"); for (int i = 0; i < sizeof(s) / 4; i++) printf("%3d", s[i]); printf("\n"); break; case 2: SelectSort(s, sizeof(s) / 4); printf("选择排序结果:\n"); for (int i = 0; i < sizeof(s) / 4; i++) printf("%3d", s[i]); printf("\n"); break; case 3: InsertSort(s, sizeof(s) / 4); printf("插入排序结果:\n"); for (int i = 0; i < sizeof(s) / 4; i++) printf("%3d", s[i]); printf("\n"); break; case 4: ShellSort(s, sizeof(s) / 4); printf("希尔排序结果:\n"); for (int i = 0; i < sizeof(s) / 4; i++) printf("%3d", s[i]); printf("\n"); break; case 5: MergeSortTop(s, sizeof(s) / 4); printf("归并排序自顶而下结果:\n"); for (int i = 0; i < sizeof(s) / 4; i++) printf("%3d", s[i]); printf("\n"); break; case 6: MergeSortBottom(s, sizeof(s) / 4); printf("归并排序自底而上结果:\n"); for (int i = 0; i < sizeof(s) / 4; i++) printf("%3d", s[i]); printf("\n"); break; case 7: QuickSort(s, sizeof(s) / 4); printf("快速排序结果:\n"); for (int i = 0; i < sizeof(s) / 4; i++) printf("%3d", s[i]); printf("\n"); break; case 8: HeapSort(s, (sizeof(s) / 4) - 1); printf("堆排序自顶而下结果:\n"); for (int i = 1; i < sizeof(s) / 4; i++) printf("%3d", s[i]); printf("\n"); break; default: break; } system("pause"); }
9.算法稳定性,时间复杂度和空间复杂度总结: