使用Obsidian配合Hexo写博客
堆排序算法CSharp实现
堆排序算法(C#实现)
Excerpt
在软件设计相关领域,“堆(Heap)”的概念主要涉及到两个方面:一种是数据结构,逻辑上是一颗完全二叉树,存储上是一个数组对象(二叉堆)。另一种是垃圾收集存储区,是软件系统可以编程的内存区域。本文所说的堆指的是前者,另外,这篇文章中堆中元素的值均以整形为例堆排序的时间复杂度是O(nlog2n),与快速
在软件设计相关领域,“堆(Heap)”的概念主要涉及到两个方面:
一种是数据结构,逻辑上是一颗完全二叉树,存储上是一个数组对象(二叉堆)。
另一种是垃圾收集存储区,是软件系统可以编程的内存区域。
本文所说的堆指的是前者,另外,这篇文章中堆中元素的值均以整形为例
堆排序的时间复杂度是O(nlog2n),与快速排序达到相同的时间复杂度. 但是在实际应用中,我们往往采用快速排序而不是堆排序. 这是因为快速排序的一个好的实现,往往比堆排序具有更好的表现. 堆排序的主要用途,是在形成和处理优先级队列方面. 另外, 如果计算要求是类优先级队列(比如, 只要返回最大或者最小元素, 只有有限的插入要求等), 堆同样是很适合的数据结构.
**堆排序
**堆排序是一种选择排序。是不稳定的排序方法。时间复杂度为O(nlog2n)。
堆排序的特点是:在排序过程中,将排序数组看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲节点和孩子节点之间的内在关系,在当前无序区中选择关键字最大(或最小)的记录。
基本思想
1.将要排序的数组创建为一个大根堆。大根堆的堆顶元素就是这个堆中最大的元素。
2.将大根堆的堆顶元素和无序区最后一个元素交换,并将无序区最后一个位置例入有序区,然后将新的无序区调整为大根堆。
重复操作,无序区在递减,有序区在递增。
初始时,整个数组为无序区,第一次交换后无序区减一,有序区增一。
每一次交换,都是大根堆的堆顶元素插入有序区,所以有序区保持是有序的。
大根堆和小根堆
堆:是一颗完全二叉树。
大根堆:所有节点的子节点比其自身小的堆
小根堆:所有节点的子节点比其自身大的堆
堆与数组的关系
堆是一种逻辑结构(形象的表示数据的存储格式),数组则是数据的实际存储结构(对应数据的存储地址),堆中的根节点与左右子节点在存储数组中的位置关系如下:假设根节点在数组中的位置(数组下标)为 i ,那么左节点在数组中的位置(数组下标)为 i * 2 + 1 , 右节点在数组中的位置(数组下标)为 i * 2 + 2 。
以上是基本的知识点,具体代码如下所示:

1 | <span> //</span><span>堆排序算法(传递待排数组名,即:数组的地址。故形参数组的各种操作反应到实参数组上)</span><span><br></span><span> private </span><span>static </span><span>void</span><span> HeapSortFunction(</span><span>int</span><span>[] array)<br> {<br> </span><span>try</span><span><br> {<br> BuildMaxHeap(array); </span><span>//</span><span>创建大顶推(初始状态看做:整体无序)</span><span><br></span><span> for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> array.Length </span><span>-</span><span>1</span><span>; i </span><span>></span><span>0</span><span>; i</span><span>--</span><span>)<br> {<br> Swap(</span><span>ref</span><span> array[</span><span>0</span><span>], </span><span>ref</span><span> array[i]); </span><span>//</span><span>将堆顶元素依次与无序区的最后一位交换(使堆顶元素进入有序区)</span><span><br></span><span> MaxHeapify(array, </span><span>0</span><span>, i); </span><span>//</span><span>重新将无序区调整为大顶堆</span><span><br></span><span> }<br> }<br> </span><span>catch</span><span> (Exception ex)<br> { }<br> }<br><br> </span><span>///</span><span><summary></span><span><br> </span><span>///</span><span> 创建大顶推(根节点大于左右子节点)<br> </span><span>///</span><span></summary></span><span><br> </span><span>///</span><span><param name="array"></span><span>待排数组</span><span></param></span><span><br></span><span> private </span><span>static </span><span>void</span><span> BuildMaxHeap(</span><span>int</span><span>[] array)<br> {<br> </span><span>try</span><span><br> {<br> </span><span>//</span><span>根据大顶堆的性质可知:数组的前半段的元素为根节点,其余元素都为叶节点</span><span><br></span><span> for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> array.Length </span><span>/</span><span>2</span><span>-</span><span>1</span><span>; i </span><span>>=</span><span>0</span><span>; i</span><span>--</span><span>) </span><span>//</span><span>从最底层的最后一个根节点开始进行大顶推的调整</span><span><br></span><span> {<br> MaxHeapify(array, i, array.Length); </span><span>//</span><span>调整大顶堆</span><span><br></span><span> }<br> }<br> </span><span>catch</span><span> (Exception ex)<br> { }<br> }<br><br> </span><span>///</span><span><summary></span><span><br> </span><span>///</span><span> 大顶推的调整过程<br> </span><span>///</span><span></summary></span><span><br> </span><span>///</span><span><param name="array"></span><span>待调整的数组</span><span></param></span><span><br> </span><span>///</span><span><param name="currentIndex"></span><span>待调整元素在数组中的位置(即:根节点)</span><span></param></span><span><br> </span><span>///</span><span><param name="heapSize"></span><span>堆中所有元素的个数</span><span></param></span><span><br></span><span> private </span><span>static </span><span>void</span><span> MaxHeapify(</span><span>int</span><span>[] array, </span><span>int</span><span> currentIndex, </span><span>int</span><span> heapSize)<br> {<br> </span><span>try</span><span><br> {<br> </span><span>int</span><span> left </span><span>=</span><span>2</span><span>*</span><span> currentIndex </span><span>+</span><span>1</span><span>; </span><span>//</span><span>左子节点在数组中的位置</span><span><br></span><span> int</span><span> right </span><span>=</span><span>2</span><span>*</span><span> currentIndex </span><span>+</span><span>2</span><span>; </span><span>//</span><span>右子节点在数组中的位置</span><span><br></span><span> int</span><span> large </span><span>=</span><span> currentIndex; </span><span>//</span><span>记录此根节点、左子节点、右子节点 三者中最大值的位置</span><span><br></span><span><br> </span><span>if</span><span> (left </span><span><</span><span> heapSize </span><span>&&</span><span> array[left] </span><span>></span><span> array[large]) </span><span>//</span><span>与左子节点进行比较</span><span><br></span><span> {<br> large </span><span>=</span><span> left;<br> }<br> </span><span>if</span><span> (right </span><span><</span><span> heapSize </span><span>&&</span><span> array[right] </span><span>></span><span> array[large]) </span><span>//</span><span>与右子节点进行比较</span><span><br></span><span> {<br> large </span><span>=</span><span> right;<br> }<br> </span><span>if</span><span> (currentIndex </span><span>!=</span><span> large) </span><span>//</span><span>如果 currentIndex != large 则表明 large 发生变化(即:左右子节点中有大于根节点的情况)</span><span><br></span><span> {<br> Swap(</span><span>ref</span><span> array[currentIndex], </span><span>ref</span><span> array[large]); </span><span>//</span><span>将左右节点中的大者与根节点进行交换(即:实现局部大顶堆)</span><span><br></span><span> MaxHeapify(array, large, heapSize); </span><span>//</span><span>以上次调整动作的large位置(为此次调整的根节点位置),进行递归调整</span><span><br></span><span> }<br> }<br> </span><span>catch</span><span> (Exception ex)<br> { }<br> }<br><br> </span><span>///</span><span><summary></span><span><br> </span><span>///</span><span> 交换函数<br> </span><span>///</span><span></summary></span><span><br> </span><span>///</span><span><param name="a"></span><span>元素a</span><span></param></span><span><br> </span><span>///</span><span><param name="b"></span><span>元素b</span><span></param></span><span><br></span><span> private </span><span>static </span><span>void</span><span> Swap(</span><span>ref</span><span>int</span><span> a, </span><span>ref</span><span>int</span><span> b)<br> {<br> </span><span>int</span><span> temp </span><span>=</span><span>0</span><span>;<br> temp </span><span>=</span><span> a;<br> a </span><span>=</span><span> b;<br> b </span><span>=</span><span> temp;<br> }</span> |

C#排序算法的比较
C#排序算法的比较
首先通过图表比较不同排序算法的时间复杂度和稳定性。
排序方法 | 平均时间 | 最坏情况 | 最好情况 | 辅助空间 | 稳定性 |
| 直接插入排序 | O(n2) | O(n2) | O(n) | O(1) | 是 |
| 冒泡排序 | O(n2) | O(n2) | O(n) | O(1) | 是 |
| 简单选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 是 |
| 希尔排序 | - | O(nlog2n)~O(n2) | O(nlog2n)~O(n2) | O(1) | 否 |
| 快速排序 | O(nlog2n) | O(n2) | O(nlog2n) | O(log2n) | 否 |
| 堆排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(1) | 否 |
| 2-路归并排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(n) | 是 |
| 基数排序 | O(d(n + rd)) | O(d(n + rd)) | O(d(n + rd)) | O(rd) | 是 |
注:1. 算法的时间复杂度一般情况下指最坏情况下的渐近时间复杂度。
2. 排序算法的稳定性会对多关键字排序产生影响。
下面通过C#代码说明不同的排序算法
插入排序
时间复杂度:平均情况—O(n2) 最坏情况—O(n2) 辅助空间:O(1) 稳定性:稳定
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
希尔排序(shell)
时间复杂度:理想情况—O(nlog2n) 最坏情况—O(n2) 稳定性:不稳定
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
冒泡排序
时间复杂度:平均情况—O(n2) 最坏情况—O(n2) 辅助空间:O(1) 稳定性:稳定
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
快速排序
时间复杂度:平均情况—O(nlog2n) 最坏情况—O(n2) 辅助空间:O(log2n) 稳定性:不稳定
快速排序有两个方向,左边的i下标一直往右走,当a[i] <= a[center_index],其中center_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走,当a[j] > a[center_index]。如果i和j都走不动了,i <= j, 交换a[i]和a[j],重复上面的过程,直到i>j。 交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 5 3 3 4 3 8 9 10 11, 现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j]交换的时刻。
选择排序
时间复杂度:平均情况—O(n2) 最坏情况—O(n2) 辅助空间:O(1) 稳定性:不稳定
选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。
堆排序
时间复杂度:平均情况—O(nlog2n) 最坏情况—O(nlog2n) 辅助空间:O(1) 稳定性:不稳定
我们知道堆的结构是节点i的孩子为2*i和2*i+1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n的序列,堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n/2-1, n/2-2, …1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法
归并排序
时间复杂度:平均情况—O(nlog2n) 最坏情况—O(nlog2n) 辅助空间:O(n) 稳定性:稳定
归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定性。那么,在短的有序序列合并的过程中,稳定是是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。
C#经典排序算法大全
C# 经典排序算法大全
Excerpt
文章浏览阅读84次。C# 经典排序算法大全选择排序using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace sorter{ public class SelectionSorter { private int min; …_c# case复杂排序
C# 经典排序算法大全
选择排序
1 | using System; |
冒泡排序
1 | using System; |
高速排序
1 | using System; |
插入排序
1 | using System; |
希尔排序
1 | using System; |
归并排序
1 | using System; |
// int row3 = TempPointsSymbol.GetLength(0); // PointsSymbol[row3, 0] = PointsSymbol[rows, 0]; // PointsSymbol[row3, 1] = PointsSymbol[rows, 1]; // //后面的清零 // for (int row4 = row3 + 1; row4 < PointsSymbol.GetLength(0); row4++) // { // for (int col4 = 0; col4 < 2; col4++) // PointsSymbol[row4, col4] = 0; // } //} } public int[,] LinearPoints(int[] Array) { Groups = 1; int StartPoint = 0; int row = 0; int col = 0; //最糟糕的情况就是有Array.Length行。 int[,] PointsSet = new int[Array.Length, 2]; //线性扫描Array,划分数组 //初始前index=0 PointsSet[row, col] = 0; do { //推断升序子数组终于的index开关 bool Judge = false; //从Array第二个数推断是否要结束或者是否是升序子数组. while (++StartPoint < Array.Length && Array[StartPoint] < Array[StartPoint - 1]) { //打开第一个升序子数组结束的index开关 Judge = true; //又一次開始第二个升序子数组的前index PointsSet[row, col + 1] = StartPoint - 1; //计算子数组个数 Groups++; //换行记录自然子数组的index row++; break; //–StartPoint; } //升序子数组结束index if (Judge) PointsSet[row, col] = StartPoint; //else // –StartPoint; } while (StartPoint < Array.Length); //终于index=StartPoint - 1,可是糟糕情况下还有剩余若干行为: 0,0 … PointsSet[row, col + 1] = StartPoint - 1; //调用展示方法 DisplaySubarray(Array, PointsSet, Groups); return PointsSet; } public void DisplaySubarray(int[] Array, int[,] PointsSet, int Groups) { Console.WriteLine(“Subarray is {0}:”, Groups); //展示子数组的前后index for (int r = 0; r < Groups; r++) { for (int c = 0; c < PointsSet.GetLength(1); c++) { Console.Write(PointsSet[r, c]); if (c < PointsSet.GetLength(1) - 1) Console.Write(“,”); } Console.Write(“\t\t”); } Console.WriteLine(); //展示分出的子数组 for (int v = 0; v < Groups; v++) { int i = 1; for (int r = PointsSet[v, 0]; r <= PointsSet[v, 1]; r++) { Console.Write(Array[r] + “ “); i++; } if (i <= 3) Console.Write(“\t\t”); else Console.Write(“\t”); if (PointsSet[v, 1] == Array.Length) break; } } public void Copy(int[] Array, int[] merge) { //一部分排好序的元素替换掉原来Array中的元素 for (int i = 0; i < Array.Length; i++) { Array[i] = merge[i]; } } //输出 public override string ToString() { string temporary = string.Empty; foreach (var element in Array27) temporary += element + “ “; temporary += “\n”; return temporary; } } class Program { static void Main(string[] args) { while (true) { Console.WriteLine(“请选择:”); Console.WriteLine(“1.归并排序(非递归)”); Console.WriteLine(“2.归并排序(递归)”); Console.WriteLine(“3.归并排序(自然合并)”); Console.WriteLine(“4.退出”); int Arraynum = Convert.ToInt32(Console.ReadLine()); switch (Arraynum) { case 4: Environment.Exit(0); break; case 1: Console.WriteLine(“Please Input Array Length”); int Leng271 = Convert.ToInt32(Console.ReadLine()); Function obj1 = new Function(Leng271); Console.WriteLine(“The original sequence:”); Console.WriteLine(obj1); Console.WriteLine(“‘MergeSort’ Finaly Sorting Result:”); obj1.ToMergeSort(); Console.WriteLine(obj1); break; case 2: Console.WriteLine(“Please Input Array Length”); int Leng272 = Convert.ToInt32(Console.ReadLine()); Function obj2 = new Function(Leng272); Console.WriteLine(“The original sequence:”); Console.WriteLine(obj2); Console.WriteLine(“‘RecursiveMergeSort’ Finaly Sorting Result:”); obj2.ToRecursiveMergeSort(); Console.WriteLine(obj2); break; case 3: Console.WriteLine(“Please Input Array Length”); int Leng273 = Convert.ToInt32(Console.ReadLine()); Function obj3 = new Function(Leng273); Console.WriteLine(“The original sequence:”); Console.WriteLine(obj3); obj3.ToNaturalMergeSort(); Console.WriteLine(); Console.WriteLine(); Console.WriteLine(“‘NaturalMergeSort’ Finaly Sorting Result:”); Console.WriteLine(obj3); break; } } } } }
基数排序
1 | using System; |
计数排序
1 | using System; |
/// 要求: /// arrayToSort的元素必须大于等于0。或者经过一定的转换使其元素在 /// 大于等于0范围内。比如有例如以下序列(-1,-8,10,11),那么依据最小值8, /// 将各个数字加8转化为(7,0,18,19),然后进行计数排序。结果为(0,7,18,19), /// 然后再将结果个数字减8即为(-8,-1,10,11) /// /// 要排序的数组 /// 数组的最大值加一 ///
堆排序
1 | using System; |
排序的分类/稳定性/时间复杂度和空间复杂度总结
版权声明:本文博客原创文章。博客,未经同意,不得转载。
冒泡排序算法CSharp实现
冒泡排序算法(C#实现) - Eric Sun - 博客园
Excerpt
简单的冒泡排序算法,代码如下://冒泡排序(从数组的起始位置开始遍历,以大数为基准:大的数向下沉一位)privatestaticvoid BubbleSortFunction(int[] array) { try { int length = array.Length; int temp; bool
简单的冒泡排序算法,代码如下:

1 | <span>//</span><span>冒泡排序(从数组的起始位置开始遍历,以大数为基准:大的数向下沉一位)</span><span><br></span><span>private</span><span>static</span><span>void</span><span> BubbleSortFunction(</span><span>int</span><span>[] array)<br> {<br> </span><span>try</span><span><br> {<br> </span><span>int</span><span> length </span><span>=</span><span> array.Length;<br> </span><span>int</span><span> temp;<br> </span><span>bool</span><span> hasExchangeAction; </span><span>//</span><span>记录此次大循环中相邻的两个数是否发生过互换(如果没有互换,则数组已经是有序的)</span><span><br></span><span><br> </span><span>for</span><span> (</span><span>int</span><span> i </span><span>=</span><span>0</span><span>; i </span><span><</span><span> length </span><span>-</span><span>1</span><span>; i</span><span>++</span><span>) </span><span>//</span><span>数组有N个数,那么用N-1次大循环就可以排完</span><span><br></span><span> {<br> hasExchangeAction </span><span>=</span><span>false</span><span>; </span><span>//</span><span>每次大循环都假设数组有序</span><span><br></span><span><br> </span><span>for</span><span> (</span><span>int</span><span> j </span><span>=</span><span>0</span><span>; j </span><span><</span><span> length </span><span>-</span><span> i </span><span>-</span><span>1</span><span>; j</span><span>++</span><span>) </span><span>//</span><span>从数组下标0处开始遍历,(length - i - 1 是刨除已经排好的大数)</span><span><br></span><span> {<br> </span><span>if</span><span> (array[j] </span><span>></span><span> array[j </span><span>+</span><span>1</span><span>]) </span><span>//</span><span>相邻两个数进行比较,如果前面的数大于后面的数,则将这相邻的两个数进行互换</span><span><br></span><span> {<br> temp </span><span>=</span><span> array[j];<br> array[j] </span><span>=</span><span> array[j </span><span>+</span><span>1</span><span>];<br> array[j </span><span>+</span><span>1</span><span>] </span><span>=</span><span> temp;<br> hasExchangeAction </span><span>=</span><span>true</span><span>; </span><span>//</span><span>发生过互换</span><span><br></span><span> }<br> }<br><br> </span><span>if</span><span> (</span><span>!</span><span>hasExchangeAction) </span><span>//</span><span>如果没有发生过互换,则数组已经是有序的了,跳出循环</span><span><br></span><span> {<br> </span><span>break</span><span>;<br> }<br> }<br> }<br> </span><span>catch</span><span> (Exception ex)<br> { }<br> }</span> |

。。。。。