排序是计算机程序设计中的一种重要操作,是把一个没有序的记录序列重新成按记录的某个关键码有序的序列的过程。排序方法按涉及的存储器不同分为内部排序和外部排序两类。内部排序指记录存放在内存中并且在内存中调整记录之间的相对位置,没有内、外存的数据交换。外部存中,借助于内存调整记录之间的相对位置,需要在内、外存之间交换数据。
排序方法按记录在排序前后的位置关系是否一致,分为稳定排序和不稳定排,稳定排序方法在排序前后相同关键码值的记录之间的位置关系不变,不稳定排序方法在排序前后相同关键码值的记录之间的位置关系改变。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SeqListSort { class Program { static void Main(string[] args) { // 测试 List<int> list = new List<int>(); Random r = new Random(); for (int i = 0; i < 10; ++i ) { //list.Add(r.Next(1,150)); if (5 == i) { list.Add(20); } else if (8 == i) { list.Add(60); } else list.Add(i + 1); } int[] a = new int[]{78,23,11,56,89,2,11,45,99,33}; int[] c = new int[a.Length]; //int[] a1 = new int[] { 1, 2, 4, 5, 8, 9 }; //int[] a2 = new int[] { 10, 11, 12, 13, 14, 15 }; //int[] c = new int[a1.Length + a2.Length]; //ArraySort(a1, a2, a1.Length, a2.Length, c); //foreach (int x in c) //{ // Console.WriteLine(x); //} MergeSort(a, a.Length); foreach (int x in a) { Console.WriteLine(x); } //SimpleSelectSort(list); QuickSort(list, 0, list.Count - 1); foreach (int x in list) { Console.WriteLine(x); } Console.ReadLine(); } public static void BubbleSort( List<int> sqList ) { bool flag = true; for (int i = 1; i < sqList.Count; ++i ) { flag = true;// 如果是一个有序的,则,内循环只循环一次,时间复杂度为O(n) for (int j = 0; j < sqList.Count - i - 1; ++j )//最后一个数没有下一个数,所以减 1 { if (sqList[j] < sqList[j+1])//比较前一个数和后一个数,大则交换 { int temp = sqList[j]; sqList[j] = sqList[j + 1]; sqList[j + 1] = temp; flag = false; } } if (flag) break; } } // 插入排序 public static void InsterSort(List<int> sqList) { for (int i = 0; i < sqList.Count; ++i) { int temp = sqList[i]; int j = 0; // 如果已经排好序的,则内循环不执行,时间复杂度O(n) for (j = i - 1; i >= 0 && temp < sqList[j]; --j )//每次将带插入的和已经排序好的比较 { sqList[j + 1] = sqList[j];//将大数放到最后 } sqList[j] = temp;//将小数插到前面 } } // 比较排序 public static void SimpleSelectSort(List<int> sqList) { int k = 0; for (int i = 0; i < sqList.Count - 1; ++i ) { k = i; for (int j = i + 1; j < sqList.Count; ++j ) { if (sqList[k] > sqList[j]) { k = j;// 交换下标,用较小的数继续放下比 } } int temp = sqList[i];//交换第i个位置和最小位置的数据 sqList[i] = sqList[k]; sqList[k] = temp; } } // 快速排序 /* 该方法的基本思想是: 1.先从数列中取出一个数作为基准数。 2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。 3.再对左右区间重复第二步,直到各区间只有一个数。 */ public static void QuickSort(List<int> sqList, int low, int high) { int i = low;//顺序表低位 即表头 int j = high;//高位 即表尾 int tmp = sqList[low];//基准数 while (low < high)// 头和尾不能相等 { while ((low < high) && (sqList[high] > tmp))//将比这个数大的数全放到它的右边 { --high;//往中间移动 } if ( low < high ) { sqList[low] = sqList[high];//将小于tmp的数放到基准数的坑里, ++low;// 低位往高位走 } while ((low < high) && (sqList[low] <= tmp))//小于或等于它的数全放到它的左边 { ++low;//往中间移动 } if ( low < high ) { sqList[high] = sqList[low];//将大于tmp的数放到高位空出来的坑里 --high;//高位往低位走 } } // 将基准数放到中间的坑里 sqList[low] = tmp;// 第一遍结束时low = high = 5,并且sqList[5]左边的比他小,右边的比他大 if (i < low) QuickSort(sqList, i, low - 1);//左侧无序子区是sqList[0....4] if (high < j) QuickSort(sqList, high + 1, j);// 右侧无序子区sqList[6....9] } // 归并排序 ,是将两个或者多个有序序列,合并成一个有序序列 // 我们先将两个有序数组合并一下,从简单开始 合并有序数列 public static void ArraySort(int[] a1, int[] a2, int len1, int len2, int[] c) { int i = 0, j = 0, k = 0; while (i < len1 && j < len2) { c[k++] = a1[i] < a2[j] ? a1[i++] : a2[j++]; } while ( i < len1 ) { c[k++] = a1[i++]; } while (j < len2) { c[k++] = a2[j++]; } } //将有二个有序数列a[first...mid]和a[mid+1...last]合并。 //这里代表一次归并,把两个位置相邻的有序子集合并成一个有序子表 public static void MergeArray(int[] a, int first, int m, int last, int[] temp) { int i = first, j = m , k = 0, n = last; while ( i < m && j < n ) { temp[k++] = a[i] < a[j] ? a[i++] : a[j++]; } while ( i < m ) { temp[k++] = a[i++]; } while ( j < n ) { temp[k++] = a[j++]; } // 查找first》0的情况 ,把剩下的元素放进来 for (i = 0; i < k; i++) a[first + i] = temp[i]; } // 一趟归并排序:相邻有序子表依次两两归并 /* 可以将a[first,mid]和a[mid+1,last]组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序, 然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。 */ public static void MergeSort(int[] a, int first, int last, int[] temp) { if ( first < last ) { int mid = (first + last) >> 1; MergeSort(a, first, mid, temp);//左边有序 MergeSort(a, mid + 1, last, temp);// 右边有序 //测试 Console.WriteLine("Secondfirst = " + first + "mid = " + mid +" last = " + last); MergeArray(a, first, mid, last, temp); // 再将二个有序数列合并 } } // 获取归并排序后的序列 public static void MergeSort(int[] a, int n) { int[] c = new int[n]; MergeSort(a, 0, n - 1, c); for (int i = 0; i < n; ++i ) { a[i] = c[i]; } } // 建堆 public static void CreateHeap(List<int> sqList, int low, int high) { if ((low < high) && (high <= sqList.Count)) { int j = 0; int tmp = 0; int k = 0; // 外循环创建最小堆
for (int i = high >> 1; i >= low; --i) { k = i; j = 2 * k + 1; tmp = sqList[i]; while (j <= high) { // 从i节点开始调整,high为节点总数 从0开始计算 i节点的子节点为 2*i+1, 2*i+2
if ((j < high) && (j + 1 < high) && (sqList[j] < sqList[j + 1]))//查找左右结点中较小的数 { ++j; } if (tmp < sqList[j]) { sqList[k] = sqList[j];//把较小的子结点往上移动,替换它的父结点 k = j; j = 2 * k + 1; } else { j = high + 1; } } sqList[k] = tmp; } } } // 堆排序的算法如下所示,算法中记录的比较表示记录关键码的比较,顺序表中只存放了记录的关键码: public static void HeapSort(List<int> sqList) { int tmp = 0; CreateHeap(sqList, 0, sqList.Count - 1); for (int i = sqList.Count - 1; i > 0; --i) { tmp = sqList[0]; sqList[0] = sqList[i]; sqList[i] = tmp; CreateHeap(sqList, 0, i - 1); } } } }
各种排序方法的比较与讨论
排序在计算机程序设计中非常重要,上面介绍的各种排序方法各有优缺点,适用的场合也各不相同。在选择排序方法时应考虑的因素有:
(1)待排序记录的数目n的大小;
(2)记录本身除关键码外的其它信息量的大小;
(3)关键码的情况;
(4)对排序稳定性的要求;
(5)语言工具的条件,辅助空间的大小等。
综合考虑以上因素,可以得出如下结论:
(1)若排序记录的数目n较小(如n≤50)时,可采用直接插入排序或简单选择排序。由于直接插入排序所需的记录移动操作较简单选择排序多,因而当记录本身信息量较大时,用简单选择排序比较好。
(2)若记录的初始状态已经按关键码基本有序,可采用直接插入排序或冒泡排序。
(3)若排序记录的数目n较大,则可采用时间复杂度为O(nlog2n)的排序方法(如快速排序、堆排序或归并排序等)。快速排序的平均性能最好,在待排序序列已经按关键码随机分布时,快速排序最适合。快速排序在最坏情况下的时间复杂度是O(n2),而堆排序在最坏情况下的时间复杂度不会发生变化,并且所需的辅助空间少于快速排序。但这两种排序方法都是不稳定的排序,若需要稳定的排序方法,则可采用归并排序。
(4)前面讨论的排序算法都是采用顺序存储结构。在待排序的记录非常多时,为避免花大量的时间进行记录的移动,可以采用链式存储结构。直接插入排序和归并排序都可以非常容易地在链表上实现,但快速排序和堆排序却很难在链表上实现。
主要介绍了常用的内部排序方法,包括三种简单排序方法,即直接插入排序、冒泡排序和简单选择排序,这三种排序方法在最好情况下的时间复杂度为O(n),在平均情况下和最坏情况下的时间复杂度都为O(n2),并且都是稳定的排序方法。
快速排序方法的平均性能最好,时间复杂度为O(nlog2n),所以,当待排序序列已经按关键码随机分布时,快速排序是最适合的。但快速排序在最坏情况下的时间复杂度是O(n
2)。快速排序方法是不稳定的排序方法
堆排序方法在最好情况下、平均情况下和最坏情况下的时间复杂度不会发生变化,为O(nlog2n),并且所需的辅助空间少于快速排序方法。堆排序方法也是不稳定的排序方法。
在实际中由于堆排序不如快速排序,所以用的很好,但堆排序在查找最大值和最小值 时,时间复杂度是O(log n),其他算法是O(n),所以在实际中 如调度算法,用来求最有调度,或者 取时间最小、等待时间最长等等。
归并排序方法在最好情况下、平均情况下和最坏情况下的时间复杂度不会发生变化,为O(nlog2n),但需要的辅助空间大于堆排序方法,但归并排序方法是稳定的排序方法。