排序算法总结(c#版)
来源:互联网 发布:电工理论与新技术 知乎 编辑:程序博客网 时间:2024/05/16 19:29
算法质量的衡量标准:
1:时间复杂度:分析关键字比较次数和记录的移动次数;
2:空间复杂度:需要的辅助内存;
3:稳定性:相同的关键字计算后,次序是否不变。
排序的分类
1:选择排序(直接选择排序、堆排序)
2:交换排序(冒泡排序、快速排序)
3:插入排序(直接插入排序、折半插入排序、Shell排序)
4:归并排序
5:捅式排序
6:基数排序
1:直接选择排序
思路:
第一次从R[0]~R[n-1]中选取最小值,与R[0]交换,第二次从R{1}~R[n-1]中选取最小值,与R[1]交换,...., 第i次从R[i-1]~R[n-1]中选取最小值,与R[i-1]交换,.....,第n-1次从R[n-2]~R[n-1]中选取最小值,与R[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。
效率分析:
在直接选择排序中,共需要进行n-1次选择和交换,每次选择需要进行 n-i 次比较 (1<=i<=n-1),而每次交换最多需要3次移动,因此,总的比较次数C=1/2(n*n - n),
总的移动次数 3(n-1).由此可知,直接选择排序的时间复杂度为 O(n2) (n的平方),所以当记录占用字节数较多时,通常比直接插入排序的执行速度快些。
由于在直接选择排序中存在着不相邻元素之间的互换,因此,直接选择排序是一种不稳定的排序方法。
代码:
private static void SelectSort(int[] data) { int arrayLength = data.Length; //依次进行n-1次比较,第i次比较将第i大的值选出放在i的位置上 for (int i = 0; i < arrayLength - 1; i++) { //保留最小值的索引 int minIndex = i; //将第i个数据和它后面的数据比较 for (int j = i + 1; j < arrayLength; j++) { if (data[minIndex] > data[j]) { minIndex = j; } } //交换数据 if (minIndex != i) { int temp = data[i]; data[i] = data[minIndex]; data[minIndex] = temp; } } }
2:堆排序
思路:
堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。
用大根堆排序的基本思想
① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。
……
直到无序区只有一个元素为止。
效率分析:
排序的时间,主要由建立初始]堆和反复重建堆这两部分的时间开销构成,它们均是通过调用Heapify实现的。
堆排序的最坏时间复杂度为O(nlogn)。堆序的平均性能较接近于最坏性能。
由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
堆排序是就地排序,辅助空间为O(1),
它是不稳定的排序方法。
代码:
private static void HeapSort(int[] data) { int arrayLength = data.Length; //建堆 for (int i = 0; i < arrayLength - 1; i++) { //建堆 BuildMaxdHeap(data, arrayLength - 1 - i); //交换堆顶和最后一个元素 Swap(data, 0, arrayLength - 1 - i); } } private static void BuildMaxdHeap(int[] data, int lastIndex) { //从最后一个节点的父节点开始 for (int i = (lastIndex - 1) / 2; i >= 0; i--) { //当前位置 int k = i; //如果当前k节点存在子节点 while (k * 2 + 1 <= lastIndex) { int biggerIndex = k * 2 + 1; //判断右子节点是否存在 if (biggerIndex < lastIndex) { if (data[biggerIndex] < data[biggerIndex + 1]) biggerIndex++; } if (data[k] < data[biggerIndex]) { Swap(data, k, biggerIndex); k = biggerIndex; } else { break; } } } } private static void Swap(int[] data, int i, int j) { int temp = data[i]; data[i] = data[j]; data[j] = temp; }
3:冒泡排序
思路:
次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。至此第一趟结束,将最大的数放到了最后。在第二趟:仍从第一对数开始比较(因为可能由于第2个数和第3个数的交换,使得第1个数不再小于第2个数),将小数放前,大数放后,一直比较到倒数第二个数(倒数第一的位置上已经是最大的),第二趟结束,在倒数第二的位置上得到一个新的最大数(其实在整个数列中是第二大的数)。如此下去,重复以上过程,直至最终完成排序。
效率分析:
若记录序列的初始状态为"正序",则冒泡排序过程只需进行一趟排序,在排序过程中只需进行n-1次比较,且不移动记录;反之,若记录序列的初始状态为"逆序",则需进行n(n-1)/2次比较和记录移动。因此冒泡排序总的时间复杂度为O(n*n)。
代码:
private static void BubbleSort(int[] data) { int arrayLength = data.Length; for (int i = 0; i < arrayLength - 1; i++) { bool flag = false; for (int j = 0; j < arrayLength - 1 - i; j++) { if (data[j] > data[j + 1]) { int temp = data[j]; data[j] = data[j + 1]; data[j + 1] = temp; flag = true; } } if (!flag) { break; } } }
4:快速排序
思路:
1)设置两个变量I、J,排序开始的时候:I=0,J=N-1;
2)以第一个数组元素作为关键数据,赋值给key,即 key=A[0];
3)从J开始向前搜索,即由后开始向前搜索(J=J-1即J--),找到第一个小于key的值A[j],A[j]与A[i]交换;
4)从I开始向后搜索,即由前开始向后搜索(I=I+1即I++),找到第一个大于key的A[i],A[i]与A[j]交换;
5)重复第3、4、5步,直到 I=J; (3,4步是在程序中没找到时候j=j-1,i=i+1,直至找到为止。找到并交换的时候i, j指针位置不变。另外当i=j这过程一定正好是i+或j-完成的最后令循环结束。)
效率分析:
冒泡排序的一种改进
代码:
private static void QuickSort(int[] data) { //调用子排序 SubSort(data, 0, data.Length - 1); } //对data数组中start到end范围内的子序列进行排序 private static void SubSort(int[] data, int start, int end) { if (start < end) { //第一个元素作为分界值 int middle = data[start]; int i = start; int j = end + 1; while (true) { while (i < end && data[++i] <= middle) ; while (j > start && data[--j] >= middle) ; if (i < j) { Swap(data, i, j); } else { break; } } Swap(data, start, j); //递归进行左子排序 SubSort(data, start, j - 1); //递归进行右子排序 SubSort(data, j + 1, end); } }
5:直接插入排序
思路:
每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序。
第一趟比较前两个数,然后把第二个数按大小插入到有序表中; 第二趟把第三个数据与前两个数从前向后扫描,把第三个数按大小插入到有序表中;依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程。
直接插入排序属于稳定的排序,最坏时间复杂性为Θ(n^2),空间复杂度为O(1)。
直接插入排序是由两层嵌套循环组成的。外层循环标识并决定待比较的数值。内层循环为待比较数值确定其最终位置。直接插入排序是将待比较的数值与它的前一个数值进行比较,所以外层循环是从第二个数值开始的。当前一数值比待比较数值大的情况下继续循环比较,直到找到比待比较数值小的并将待比较数值置入其后一位置,结束该次循环
效率分析:
速度慢,空间效率高,稳定。
代码:
private static void InsertSort(int[] data) { for (int i = 1; i < data.Length; i++) { //备份data[i]的值 int temp = data[i]; if (data[i] < data[i - 1]) { int j = i - 1; //通过比较找出要插入的位置,同时将大的数值向右移动 for (; j >= 0 && data[j] > temp; j--) { data[j + 1] = data[j]; } //将temp插入合适的位置 data[j + 1] = temp; } } }
6:折半插入排序
思路:
折半插入排序(binary insertion sort)是对插入排序算法的一种改进,由于排序算法过程中,就是不断的依次将元素插入前面已排好序的序列中。由于前半部分为已排好序的数列,这样我们不用按顺序依次寻找插入点,可以采用折半查找的方法来加快寻找插入点的速度。
折半插入排序算法的具体操作为:在将一个新元素插入已排好序的数组的过程中,寻找插入点时,将待插入区域的首元素设置为a[low],末元素设置为a[high],则轮比较时将待插入元素与a[m],其中m=(low+high)/2相比较,如果比参考元素小,则选择a[low]到a[m-1]为新的插入区域(即high=m-1),否则选择a[m+1]到a[high]为新的插入区域(即low=m+1),如此直至low<=high不成立,即将此位置之后所有元素后移一位,并将新元素插入a[high+1]。
效率分析:
折半插入排序算法是一种稳定的排序算法,比直接插入算法明显减少了关键字之间比较的次数,因此速度比直接插入排序算法快,但记录移动的次数没有变,所以折半插入排序算法的时间复杂度仍然为O(n^2),与直接插入排序算法相同。
代码:
private static void BinaryInsertSort(int[] data) { for (int i = 1; i < data.Length; i++) { //备份data[i]的值 int temp = data[i]; int low = 0; int high = i - 1; while (low <= high) { //取low到high中间的索引 int middle = (low + high) / 2; //确定temp在中间值的哪侧 if (temp > data[middle]) { low = middle + 1; } else { high = middle - 1; } } //将low到i处的所有元素向后整体移一位 for (int j = i; j > low; j--) { data[j] = data[j - 1]; } //将temp插入合适的位置 data[low] = temp; } }
7:Shell排序
思路:
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中。先在各组内进行直接插人排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<;…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
效率分析:
性能由于直接插入排序,不稳定
代码:
private static void ShellSort(int[] data) { int arrayLength = data.Length; //可增变量 int h = 1; while (h <= arrayLength / 3) { h = h * 3 + 1; } while (h > 0) { for (int i = h; i < arrayLength; i++) { //备份当前值 int temp = data[i]; if (data[i] < data[i - h]) { int j = i - h; //整体向后移动h格 for (; j >= 0 && data[j] > temp; j -= h) { data[j + h] = data[j]; } data[j + h] = temp; } } h = (h - 1) / 3; } }
8:归并排序
思路:
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
设定两个指针,最初位置分别为两个已经排序序列的起始位置
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针达到序列尾
将另一序列剩下的所有元素直接复制到合并序列尾
效率分析:
速度仅次于快速排序,但较稳定
代码:
private static void MergeSort(int[] data) { Sort(data, 0, data.Length - 1); } //将left到right范围内的数组进行归并排序 private static void Sort(int[] data, int left, int right) { if (left < right) { int center = (left + right) / 2; //对左侧数组递归排序 Sort(data, left, center); //对右侧数组递归排序 Sort(data, center + 1, right); //合并 Merge(data, left, center, right); } } //将两个数组进行归并 private static void Merge(int[] data, int left, int center, int right) { int[] tempArray = new int[data.Length]; int middle = center + 1; int third = left; int temp = left; while (left <= center && middle <= right) { //从两个数组中取出小的放入中间的数组 if (data[left] <= data[middle]) { tempArray[third++] = data[left++]; } else { tempArray[third++] = data[middle++]; } } //将余下的依次放入中间数组 while (middle <= right) { tempArray[third++] = data[middle++]; } while (left <= center) { tempArray[third++] = data[left++]; } //将中间数组中的内容复制到原数组 while (temp <= right) { data[temp] = tempArray[temp++]; } }
9:捅式排序
思路:
效率分析:
代码:
private static void BucketSort(int[] data, int min, int max) { int arrayLength = data.Length; int[] temp = new int[arrayLength]; //记录待排序元素的信息 int[] buckets = new int[max - min]; //计算每个元素在序列中出现的次数 for (int i = 0; i < arrayLength; i++) { buckets[data[i] - min]++; } //计算“落入”各捅内的元素在有序序列中的位置 for (int i = 1; i < max - min; i++) { buckets[i] = buckets[i] + buckets[i - 1]; } //将当前数组备份到temp中 data.CopyTo(temp, 0); //根据buckets数组中的信息将待排序的各元素让入相应的位置 for (int k = arrayLength - 1; k >= 0; k--) { data[--buckets[temp[k] - min]] = temp[k]; } }
10:基数排序
思路:
将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。
效率分析:
时间效率:设待排序列为n个记录,d个关键码,关键码的取值范围为radix,则进行链式基数排序的时间复杂度为O(d(n+radix)),其中,一趟分配时间复杂度为O(n),一趟收集时间复杂度为O(n),共进行d趟分配和收集。 空间效率:需要2*radix个指向队列的辅助空间,以及用于静态链表的n个指针。
代码:
/* * radix 指定关键字拆分的进制,如10为十进制 * d 指定关键字拆分成几个子关键字 */ private static void RadixSort(int[] data, int radix, int d) { int arrayLength = data.Length; int[] temp = new int[arrayLength]; int[] buckets = new int[radix]; //依次从最高位的子关键字对待排数据进行排序 for (int i = 0, rate = 1; i < d; i++) { //重置临时数组 Array.Clear(buckets, 0, arrayLength); //将data数组进行备份 data.CopyTo(temp, 0); //计算每个待排序数据的子关键字 for (int j = 0; j < arrayLength; j++) { int subKey = (temp[j] / rate) % radix; buckets[subKey]++; } for (int j = 1; j < radix; j++) { buckets[j] = buckets[j] + buckets[j - 1]; } //按照子关键字对指定数据进行排序 for (int m = arrayLength - 1; m >= 0; m--) { int subKey = (temp[m] / rate) % radix; data[--buckets[subKey]] = temp[m]; } rate *= radix; } }
- 排序算法总结(c#版)
- 排序算法总结(C#版)
- C#排序算法总结
- C#算法学习总结—冒泡排序
- 排序算法(总结)
- 常见排序算法总结(java版)
- Java排序算法总结(汇总版)
- 快速排序算法c#版
- 快速排序算法-c#版
- 排序算法总结 c++版
- 排序算法总结(1)
- 排序算法总结(一)
- 排序算法总结(二)
- 排序算法总结(三)
- 排序算法总结(一)
- 排序算法总结(二)
- 排序算法总结(三)
- 排序算法总结(四)
- python 安装模块的总结
- 转一个解析linux内核I2C体系结构的例子,说的比较清楚 .
- XCode下的iOS单元测试
- 修改liunx的预读区大小
- Nginx warn] the "log_format"
- 排序算法总结(c#版)
- 【windows8开发】现有代码移植到Metro App所必须的API整合
- win下实现切换帐号的方法
- 警告: No configuration found for the specified action: 'loginPerson' in namespace: ''. Form action def
- 关于List和IList的区别和不同情况的不同用法
- 做一个像植物大战僵尸的Flash游戏5
- USACO Factorials,DP,因子分解
- 数据库连接,查询和插入数据的方法
- eclispe中debug调试程序