19 排序

知识结构:

1. 排序的基本概念与术语

假设含有nnn个记录的序列为{r1,r2,⋯,rn}\lbrace r_1,r_2,\cdots,r_n \rbrace{r1​,r2​,⋯,rn​},其相应的关键字分别为{k1,k2,⋯,kn}\lbrace k_1,k_2,\cdots,k_n \rbrace{k1​,k2​,⋯,kn​},需确定 的一种排列1,2,⋯,n1,2,\cdots,n1,2,⋯,n,使其相应的关键字满足kp1≤kp2≤⋯≤kpnk_{p_1}\leq k_{p_2}\leq\cdots\leq k_{p_n}kp1​​≤kp2​​≤⋯≤kpn​​的关系,即使得序列成为一个按关键字有序的序列 ,这个样的操作就称为 排列

能唯一标识某一记录的关键字称为 主关键字

假设ki=kj(1≤i≤n,1≤j≤n,i≠j)k_i = k_j(1\leq i \leq n, 1\leq j\leq n,i\neq j)ki​=kj​(1≤i≤n,1≤j≤n,i​=j),且在排序前的序列中rir_iri​领先于rjr_jrj​(即i<ji<ji<j )。如果排序后rir_iri​仍领先于rjr_jrj​,则称所用的排序方法是稳定的;反之,若可能使得排序后的序列中rjr_jrj​领先于rir_iri​,则称所用的排序方法是不稳定的

例如:

  • 待排序序列:20,30,20‾20,30,\overline{20}20,30,20
  • 稳定的排序结果:20,20‾,3020,\overline{20},3020,20,30
  • 不稳定的排序结果:20‾,20,30\overline{20},20,3020,20,30

内部排序:排序过程都在内存中进行的排序。

外部排序:排序过程需要在内存和外存之间不断交换数据的排序。

根据排序过程中借助的主要操作,我们把内排序分为:插入排序选择排序交换排序并归排序。可以说,这些都是比较成熟的排序技术,已经被广泛地应用于许许多多的程序语言或数据库当中,甚至它们都已经封装了关于排序算法的实现代码。因此,我们学习这些排序算法的目的不是为了去现实排序算法,而是通过学习来提高我们编写算法的能力,以便于去解决更多复杂和灵活的应用性问题。

2. 插入排序

2.1 直接插入排序

将一个记录插入到已经排好序的有序表中,从而得到一个新的记录增1的有序表。

即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。

如:int[] Key = new int[]{45,34,78,12,34,32,29,64};

排序过程:

init0→[45],34‾,78,12,34,32,29,64init0\rightarrow [45],\overline{34},78,12,34,32,29,64init0→[45],34,78,12,34,32,29,64

step1→[34‾,45],78,12,34,32,29,64step1\rightarrow [\overline{34},45],78,12,34,32,29,64step1→[34,45],78,12,34,32,29,64

step2→[34‾,45,78],12,34,32,29,64step2\rightarrow [\overline{34},45,78],12,34,32,29,64step2→[34,45,78],12,34,32,29,64

step3→[12,34‾,45,78],34,32,29,64step3\rightarrow [12,\overline{34},45,78],34,32,29,64step3→[12,34,45,78],34,32,29,64

step4→[12,34‾,34,45,78],32,29,64step4\rightarrow [12,\overline{34},34,45,78],32,29,64step4→[12,34,34,45,78],32,29,64

step5→[12,32,34‾,34,45,78],29,64step5\rightarrow [12,32,\overline{34},34,45,78],29,64step5→[12,32,34,34,45,78],29,64

step6→[12,29,32,34‾,34,45,78],64step6\rightarrow [12,29,32,\overline{34},34,45,78],64step6→[12,29,32,34,34,45,78],64

result→[12,29,32,34‾,34,45,64,78]result\rightarrow [12,29,32,\overline{34},34,45,64,78]result→[12,29,32,34,34,45,64,78]

程序代码:

/// <summary>
/// 直接插入排序
/// </summary>
/// <typeparam name="T">需要排序记录的类型</typeparam>
/// <param name="array">需要排序的记录集合</param>
public static void StraightInsertSort<T>(T[] array) where T : IComparable<T>
{for (int i = 1; i < array.Length; i++){int j = i - 1;T current = array[i];while (j >= 0 && array[j].CompareTo(current) > 0){array[j + 1] = array[j];j--;}array[j + 1] = current;}
}

如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面,所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以直接插入排序是稳定的。

2.2 希尔插入排序

希尔排序是1959年由D.L.Shell提出来的,相对直接插入排序有较大的改进。希尔插入排序又叫做缩小增量排序

直接插入排序的效率在某些时候是很高的,比如我们的记录本身就是基本有序的,我们只需要少量的插入操作,就可以完成整个记录集的排序工作,此时直接插入排序很高效。还有就是记录数比较少时,直接插入的优势也比较明显。可问题在于,两个条件本身就过于苛刻,现实中记录少或者基本有序都属于特殊情况。

不过别着急,有条件当然是好,条件不存在,我们创造条件也是可以去做的。于是科学家希尔研究出了一种排序方法,对直接插入排序改进后可以增加效率。

思想:按待排序列下标的一定增量分组(如:增量序列t1,t2,⋯,tkt_1,t_2,\cdots,t_kt1​,t2​,⋯,tk​,其中ti>tjt_i>t_jti​>tj​,tk=1t_k=1tk​=1),将整个待排序列分割成若干子序列,分别进行直接插入排序,随着增量逐渐减少,所分组包含的记录越来越多,到增量值减至1时,整个记录集被分成一组(这时待排序列“基本有序”),再对全体记录进行直接插入排序,算法终止(对待排序列进行了kkk趟直接插入排序)。

我所理解Shell Insertion Sort最牛的地方是,让排序算法能够并行化。

希尔插入排序增量的取法:

delta1=[n2]=[102]=5delta1=\left[\frac{n}{2}\right] =\left[\frac{10}{2}\right]=5delta1=[2n​]=[210​]=5

delta2=[delta12]=[52]=2delta2=\left[\frac{delta1}{2}\right]=\left[\frac{5}{2}\right]=2delta2=[2delta1​]=[25​]=2

delta3=[delta22]=[22]=1delta3=\left[\frac{delta2}{2}\right]=\left[\frac{2}{2}\right]=1delta3=[2delta2​]=[22​]=1

即:先将要排序的一组记录按某个增量deltadeltadelta([n2]\left[\frac{n}{2}\right][2n​] ,nnn为要排序数的个数)分成若干组子序列,每组中记录的下标相差deltadeltadelta。对每组中全部元素进行直接插入排序,然后在用一个较小的增量([delta2]\left[\frac{delta}{2}\right][2delta​] )对它进行分组,在每组中再进行直接插入排序。继续不断缩小增量直至为1,最后使用直接插入排序完成排序。

如:int[] Key = new int[]{45,34,78,12,34,32,29,64};

排序过程:

d=4→45,34‾,78,12,34,32,29,64d=4\rightarrow 45,\overline{34},78,12,34,32,29,64d=4→45,34,78,12,34,32,29,64

d=2→34,32,29,12,45,34‾,78,64d=2\rightarrow 34,32,29,12,45,\overline{34},78,64d=2→34,32,29,12,45,34,78,64

d=1→29,12,34,32,45,34‾,78,64d=1\rightarrow 29,12,34,32,45,\overline{34},78,64d=1→29,12,34,32,45,34,78,64

result→12,29,32,34,34‾,45,64,78result\rightarrow 12,29,32,34,\overline{34},45,64,78result→12,29,32,34,34,45,64,78

程序代码:

private static void Shell<T>(int delta, T[] array) where T : IComparable<T>
{//带增量的直接插入排序for (int i = delta; i < array.Length; i++){int j = i - delta;T current = array[i];while (j >= 0 && array[j].CompareTo(current) > 0){array[j + delta] = array[j];j = j - delta;}array[j + delta] = current;}
}/// <summary>
/// 希尔插入排序
/// </summary>
/// <typeparam name="T">需要排序记录的类型</typeparam>
/// <param name="array">需要排序的记录集合</param>
public static void ShellInsertSort<T>(T[] array) where T : IComparable<T>
{for (int delta = array.Length/2; delta > 0; delta = delta/2){Shell(delta, array);}
}

希尔插入排序时效分析很难,关键码的比较次数与记录移动次数依赖于增量因子序列deltadeltadelta的选取。目前还没有人给出选取最好的增量因子序列的方法。希尔插入排序方法是一个不稳定的排序方法。

3. 选择排序

3.1 直接选择排序

思想:每一趟在n−i(i=0,1,⋯,n−1)n-i(i=0,1,\cdots,n-1)n−i(i=0,1,⋯,n−1)个记录中选取关键字最小的记录作为有序序列中第iii个记录。(在要排列的一组数中,选出最小的一个数与第1个位置的数交换;然后在剩下的数当中再找最小的与第2个位置的数交换,依次类推,直到第n−1n-1n−1个元素和第nnn个元素比较为止。)

如:int[] Key = new int[]{45,34,78,12,34,32,29,64};

排序过程:

i=0→12,34‾,78,45,34,32,29,64i=0\rightarrow 12,\overline{34},78,45,34,32,29,64i=0→12,34,78,45,34,32,29,64

i=1→12,29,78,45,34,32,34‾,64i=1\rightarrow 12,29,78,45,34,32,\overline{34},64i=1→12,29,78,45,34,32,34,64

i=2→12,29,32,45,34,78,34‾,64i=2\rightarrow 12,29,32,45,34,78,\overline{34},64i=2→12,29,32,45,34,78,34,64

i=3→12,29,32,34,45,78,34‾,64i=3\rightarrow 12,29,32,34,45,78,\overline{34},64i=3→12,29,32,34,45,78,34,64

i=4→12,29,32,34,34‾,78,45,64i=4\rightarrow 12,29,32,34,\overline{34},78,45,64i=4→12,29,32,34,34,78,45,64

i=5→12,29,32,34,34‾,45,78,64i=5\rightarrow 12,29,32,34,\overline{34},45,78,64i=5→12,29,32,34,34,45,78,64

i=6→12,29,32,34,34‾,45,64,78i=6\rightarrow 12,29,32,34,\overline{34},45,64,78i=6→12,29,32,34,34,45,64,78

程序代码:

/// <summary>
/// 直接选择排序
/// </summary>
/// <typeparam name="T">需要排序记录的类型</typeparam>
/// <param name="array">需要排序的记录集合</param>
public static void StraightSelectSort<T>(T[] array) where T : IComparable<T>
{for (int i = 0; i < array.Length - 1; i++){int minIndex = i;T min = array[i];for (int j = i + 1; j < array.Length; j++){if (array[j].CompareTo(min) < 0){min = array[j];minIndex = j;}}if (minIndex != i){array[minIndex] = array[i];array[i] = min;}}
}

直接选择排序是不稳定的。

3.2 堆选择排序

直接选择排序并没有把每一趟的比较结果保存下来,在后一趟的比较中,许多比较在前一趟已经做过了,但由于前一趟排序时未保存这些比较结果,所以后一趟排序时又重复执行了这些比较操作,因而执行的比较次数较多。

如果可以做到每次在选择到最小记录的同时,并根据比较结果对其他记录做出相应的调整,那样排序的总体效率就会非常高了。而堆选择排序是一种树形选择排序,是对直接选择排序的有效改进。堆选择排序算法是Floyd和Williams在1964年共同发明的,同时,他们也发明了“堆”这样的数据结构。

堆的概念:具有nnn个元素的序列K={k1,k2,⋯,kn}K=\lbrace k_1,k_2,⋯,k_n \rbraceK={k1​,k2​,⋯,kn​}当且仅当满足

{ki≤k2iki≤k2i+1,{ki≥k2iki≥k2i+1,i=1,2,⋯,[n2]\begin{cases} k_i\leq k_{2i}\\ k_i\leq k_{2i+1} \end{cases}, \begin{cases} k_i\geq k_{2i}\\ k_i\geq k_{2i+1} \end{cases}, i=1,2,\cdots,\left[\frac{n}{2}\right] {ki​≤k2i​ki​≤k2i+1​​,{ki​≥k2i​ki​≥k2i+1​​,i=1,2,⋯,[2n​]

时称之为堆。

若关键码的集合K={k1,k2,⋯,kn}K=\lbrace k_1,k_2,⋯,k_n \rbraceK={k1​,k2​,⋯,kn​},把它们按照完全二叉树的顺序存放在一维数组中。

  • 若满足ki≤k2ik_i\leq k_{2i}ki​≤k2i​且ki≤k2i+1k_i\leq k_{2i+1}ki​≤k2i+1​则称作小根堆。
  • 若满足ki≥k2ik_i\geq k_{2i}ki​≥k2i​且ki≥k2i+1k_i \geq k_{2i+1}ki​≥k2i+1​则称作大根堆。

小根堆:int[] Key = new int[]{9,17,65,23,45,78,87,53,31,58,64};

大根堆:int[] Key = new int[]{94,93,75,91,85,44,51,18,48,58,10,34};

思想(以大根堆为例):
构建大根堆之后,输出堆顶记录,对剩余的n−1n-1n−1个记录接着构建大根堆,便可得到nnn个记录的次大值,如此反复执行,就能得到一个有序序列,这个过程称为堆选择排序。

堆选择排序需解决的两个问题:

问题1:如何建堆,对初始序列建堆的过程,就是一个反复进行筛选的过程。

  • (1)对一棵具有nnn个结点的完全二叉树来说最后一个结点是第[n2][\frac{n}{2}][2n​]个结点的子树。
  • (2)筛选从第[n2][\frac{n}{2}][2n​]个结点为根的子树开始,该子树成为堆。
  • (3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。

即把序号为[n2],[n2]−1,⋯,1[\frac{n}{2}],[\frac{n}{2}]-1,\cdots,1[2n​],[2n​]−1,⋯,1,的记录作为根的子树都调整为堆。

问题2:输出堆顶元素后,如何调整新堆。

  • (1)设有nnn个元素的堆,输出堆顶元素后,剩下n−1n-1n−1个元素。将堆底元素送入堆顶(最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。
  • (2)将根结点与左,右子树中较大元素进行交换。
  • (3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复第二步。
  • (4)若与右子树交换:如果右子树堆被破坏,即右子树的根结点不满足堆的性质,则重复第二步。
  • (5)继续对不满足堆性质的子树进行上述操作,直到叶子结点,堆被重建成。

即:输出堆顶元素,将最后一个叶子结点放在堆顶,重新构建大根堆。

如:int[] Key = new int[]{45,34,78,12,34,32,29,64};

排序过程:

初始时:Key1→−1,45,34‾,78,12,34,32,29,64Key1 \rightarrow -1,45,\overline{34},78,12,34,32,29,64Key1→−1,45,34,78,12,34,32,29,64

建堆后:Key1→−1,78,64,45,34‾,34,32,29,12Key1 \rightarrow -1,78,64,45,\overline{34},34,32,29,12Key1→−1,78,64,45,34,34,32,29,12

堆重建后:

Key1→−1,64,34‾,45,12,34,32,29,[78]Key1 \rightarrow -1,64,\overline{34},45,12,34,32,29,[78]Key1→−1,64,34,45,12,34,32,29,[78]

Key1→−1,45,34‾,32,12,34,29,[64,78]Key1 \rightarrow -1,45,\overline{34},32,12,34,29,[64,78]Key1→−1,45,34,32,12,34,29,[64,78]

Key1→−1,34‾,34,32,12,29,[45,64,78]Key1 \rightarrow -1,\overline{34},34,32,12,29,[45,64,78]Key1→−1,34,34,32,12,29,[45,64,78]

Key1→−1,34,29,32,12,[34‾,45,64,78]Key1 \rightarrow -1,34,29,32,12,[\overline{34},45,64,78]Key1→−1,34,29,32,12,[34,45,64,78]

Key1→−1,32,29,12,[34,34‾,45,64,78]Key1 \rightarrow -1,32,29,12,[34,\overline{34},45,64,78]Key1→−1,32,29,12,[34,34,45,64,78]

Key1→−1,29,12,[32,34,34‾,45,64,78]Key1 \rightarrow -1,29,12,[32,34,\overline{34},45,64,78]Key1→−1,29,12,[32,34,34,45,64,78]

Key1→−1,12,[29,32,34,34‾,45,64,78]Key1 \rightarrow -1,12,[29,32,34,\overline{34},45,64,78]Key1→−1,12,[29,32,34,34,45,64,78]

程序代码:

private static void Restore<T>(T[] array, int j, int vCount) where T : IComparable<T>
{//构建以结点j为根,一共有vCount个结点的大根堆while (j <= vCount / 2){int m = (2 * j + 1 <= vCount && array[2 * j + 1].CompareTo(array[2 * j]) > 0)? 2 * j + 1: 2 * j;if (array[m].CompareTo(array[j]) > 0){T temp = array[m];array[m] = array[j];array[j] = temp;j = m;}else{break;}}
}/// <summary>
/// 堆选择排序
/// </summary>
/// <typeparam name="T">需要排序记录的类型</typeparam>
/// <param name="array">需要排序的记录集合</param>
public static void HeapSelectSort<T>(T[] array) where T : IComparable<T>
{int vCount = array.Length;T[] tempArray = new T[vCount + 1];for (int i = 0; i < vCount; i++)tempArray[i + 1] = array[i];//初建大根堆for (int i = vCount / 2; i >= 1; i--)Restore(tempArray, i, vCount);//大根堆的重构与排序for (int i = vCount; i > 1; i--){T temp = tempArray[i];tempArray[i] = tempArray[1];tempArray[1] = temp;Restore(tempArray, 1, i - 1);}for (int i = 0; i < vCount; i++)array[i] = tempArray[i + 1];
}

4. 交换排序

4.1 冒泡交换排序

思想:通过相邻记录之间的比较和交换,使关键字较小的记录如气泡一般逐渐向上漂移直至水面。

如:int[] Key = new int[]{45,34,78,12,34,32,29,64};

i=0→[12],45,34‾,78,29,34,32,64i=0\rightarrow [12],45,\overline{34},78,29,34,32,64i=0→[12],45,34,78,29,34,32,64

i=1→[12,29],45,34‾,78,32,34,64i=1\rightarrow [12,29],45,\overline{34},78,32,34,64i=1→[12,29],45,34,78,32,34,64

i=2→[12,29,32],45,34‾,78,34,64i=2\rightarrow [12,29,32],45,\overline{34},78,34,64i=2→[12,29,32],45,34,78,34,64

i=3→[12,29,32,34‾],45,34,78,64i=3\rightarrow [12,29,32,\overline{34}],45,34,78,64i=3→[12,29,32,34],45,34,78,64

i=4→[12,29,32,34‾,34],45,64,78i=4\rightarrow [12,29,32,\overline{34},34],45,64,78i=4→[12,29,32,34,34],45,64,78

i=5→[12,29,32,34‾,34,45],64,78i=5\rightarrow [12,29,32,\overline{34},34,45],64,78i=5→[12,29,32,34,34,45],64,78

i=6→[12,29,32,34‾,34,45,64],78i=6\rightarrow [12,29,32,\overline{34},34,45,64],78i=6→[12,29,32,34,34,45,64],78

程序代码:

/// <summary>
/// 冒泡交换排序
/// </summary>
/// <typeparam name="T">需要排序记录的类型</typeparam>
/// <param name="array">需要排序的记录集合</param>
public static void BubbleExchangeSort<T>(T[] array) where T : IComparable<T>
{for (int i = 0; i < array.Length - 1; i++){for (int j = array.Length - 1; j > i; j--){if (array[j].CompareTo(array[j - 1]) < 0){T temp = array[j - 1];array[j - 1] = array[j];array[j] = temp;}}}
}

对冒泡排序常见的改进方法是加入一个标志性变量flag,用于标志某一趟排序过程是否有数据交换,如果进行某一趟排序时并没有进行数据交换,则说明数据已经按要求排列好,可立即结束排序,避免不必要的比较过程。

程序代码:

/// <summary>
/// 改进的冒泡交换排序
/// </summary>
/// <typeparam name="T">需要排序记录的类型</typeparam>
/// <param name="array">需要排序的记录集合</param>
public static void BubbleExchangeSortImproved<T>(T[] array) where T : IComparable<T>
{for (int i = 0; i < array.Length - 1; i++){bool flag = false;for (int j = array.Length - 1; j > i; j--){if (array[j].CompareTo(array[j - 1]) < 0){T temp = array[j - 1];array[j - 1] = array[j];array[j] = temp;flag = true;}}if (flag == false)break;}
}

4.2 快速交换排序

快速交换排序是由图灵奖获得者Tony Hoare(东尼.霍尔)所发展的一种排序算法,是采用分治策略的一个非常典型的应用。快速交换排序虽然高端,但其思想是来自冒泡交换排序的,冒泡交换排序是通过相邻元素的比较和交换把最小的冒泡到最顶端,而快速交换排序是比较和交换小数和大数,这样一来不仅小数冒泡到上面的同时也把大数沉到下面。

其基本思想如下:

  • (1)选择一个基准元素,通常选择第一个元素或者最后一个元素。
  • (2)通过一趟排序将待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小,另一部分记录的元素值比基准值大。
  • (3)此时基准元素在其排好序的正确位置。
  • (4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。

即:从待排记录中选一记录,将其放入正确的位置,然后以该位置为界,对左右两部分再做快速排序,直到划分的长度为1。

如:int[] Key = new int[]{45,34,78,12,34,32,29,64};

init→45,34‾,78,12,34,32,29,64init\rightarrow 45,\overline{34},78,12,34,32,29,64init→45,34,78,12,34,32,29,64

step1→[32,34‾,29,12,34],45,[78,64]step1\rightarrow [32,\overline{34},29,12,34],45,[78,64]step1→[32,34,29,12,34],45,[78,64]

step2→[[29,12],32,[34‾,34]],45,[78,64]step2\rightarrow [[29,12],32,[\overline{34},34]],45,[78,64]step2→[[29,12],32,[34,34]],45,[78,64]

step3→[[[12],29],32,[34‾,34]],45,[78,64]step3\rightarrow [[[12],29],32,[\overline{34},34]],45,[78,64]step3→[[[12],29],32,[34,34]],45,[78,64]

step4→[[[12],29],32,[[34],34‾]],45,[78,64]step4\rightarrow [[[12],29],32,[[34],\overline{34}]],45,[78,64]step4→[[[12],29],32,[[34],34]],45,[78,64]

step5→[[[12],29],32,[[34],34‾]],45,[[64],78]step5\rightarrow [[[12],29],32,[[34],\overline{34}]],45,[[64],78]step5→[[[12],29],32,[[34],34]],45,[[64],78]

程序代码:

private static void QuickSort<T>(T[] array, int left, int right) where T : IComparable<T>
{//快速排序递归函数if (left < right){T current = array[left];int i = left;int j = right;while (i < j){while (array[j].CompareTo(current) > 0 && i < j)j--;while (array[i].CompareTo(current) <= 0 && i < j)i++;if (i < j){T temp = array[i];array[i] = array[j];array[j] = temp;j--;i++;}}array[left] = array[j];array[j] = current;if (left < j - 1) QuickSort(array, left, j - 1);if (right > j + 1) QuickSort(array, j + 1, right);}
}/// <summary>
/// 快速交换排序
/// </summary>
/// <typeparam name="T">需要排序记录的类型</typeparam>
/// <param name="array">需要排序的记录集合</param>
public static void QuickExchangeSort<T>(T[] array) where T : IComparable<T>
{QuickSort(array, 0, array.Length - 1);
}

其实上面的代码还可以再优化,上面代码中基准元素已经在current中保存了,所以不需要每次交换都设置一个temp变量,在交换的时候只需要先后覆盖就可以了。这样既能较少空间的使用还能降低赋值运算的次数。

优化代码如下:

private static void QuickSortImproved<T>(T[] array, int left, int right) where T : IComparable<T>
{//快速排序递归函数if (left < right){T current = array[left];int i = left;int j = right;while (i < j){while (array[j].CompareTo(current) > 0 && i < j)j--;array[i] = array[j];while (array[i].CompareTo(current) <= 0 && i < j)i++;array[j] = array[i];}array[j] = current;if (left < j - 1) QuickSortImproved(array, left, j - 1);if (right > j + 1) QuickSortImproved(array, j + 1, right);}
}/// <summary>
/// 快速交换排序
/// </summary>
/// <typeparam name="T">需要排序记录的类型</typeparam>
/// <param name="array">需要排序的记录集合</param>
public static void QuickExchangeSortImproved<T>(T[] array) where T : IComparable<T>
{QuickSortImproved(array, 0, array.Length - 1);
}

5. 并归排序

我们首先先看两个有序序列合并的例子,如:

int[] Key1 = new int[]{1,3,5,7,9};
int[] Key2 = new int[]{2,4,6,8,10,12,14}int[] temp = new int[Key1.Length + Key2.Length];

temp→0,0,0,0,0,0,0,0,0,0,0,0temp \rightarrow 0,0,0,0,0,0,0,0,0,0,0,0temp→0,0,0,0,0,0,0,0,0,0,0,0

temp→1,2,3,4,5,6,7,8,9,0,0,0temp \rightarrow 1,2,3,4,5,6,7,8,9,0,0,0temp→1,2,3,4,5,6,7,8,9,0,0,0

temp→1,2,3,4,5,6,7,8,9,10,12,14temp \rightarrow 1,2,3,4,5,6,7,8,9,10,12,14temp→1,2,3,4,5,6,7,8,9,10,12,14

程序代码:

/// <summary>
/// 合并排序
/// </summary>
/// <typeparam name="T">需要排序记录的类型</typeparam>
/// <param name="array1">有序记录集合1</param>
/// <param name="array2">有序记录集合2</param>
/// <returns>合并后的有序记录集合</returns>
public static T[] MergeSort<T>(T[] array1,T[] array2) where T : IComparable<T>
{T[] temp = new T[array1.Length + array2.Length];int i = 0, j = 0, k = 0;while (i < array1.Length && j < array2.Length){if (array1[i].CompareTo(array2[i]) < 0){temp[k++] = array1[i++];}else{temp[k++] = array2[j++];}}while (i < array1.Length){temp[k++] = array1[i++];}while (j < array2.Length){temp[k++] = array2[j++];}return temp;
}

我们接着看一个序列的并归排序。

首先递归划分子问题,然后合并结果。把待排序列看成由两个有序的子序列构成,然后合并两个子序列,接着把子序列看成由两个有序序列组成……。倒过来看,其实就是先两两合并,然后四四合并……最终形成有序序列。该算法是采用分治策略的一个非常典型的应用,俗称 2-路并归

如:int[] Key = new int[]{45,34,78,12,34,32,29,64};

程序代码:

/// <summary>
/// 合并排序的递归合并函数
/// </summary>
/// <typeparam name="T">需要排序记录的类型</typeparam>
/// <param name="array">需要排序的记录集合</param>
/// <param name="left">起点位置</param>
/// <param name="mid">中间位置</param>
/// <param name="right">终点位置</param>
private static void Merge<T>(T[] array, int left, int mid, int right) where T : IComparable<T>
{T[] temp = new T[right - left + 1];int i = left;int j = mid + 1;int k = 0;while (i <= mid && j <= right){if (array[i].CompareTo(array[j]) < 0){temp[k++] = array[i++];}else{temp[k++] = array[j++];}}while (i <= mid){temp[k++] = array[i++];}while (j <= right){temp[k++] = array[j++];}for (int n = 0; n < temp.Length; n++){array[left + n] = temp[n];}
}/// <summary>
/// 合并排序的递归分治函数
/// </summary>
/// <typeparam name="T">需要排序记录的类型</typeparam>
/// <param name="array">需要排序的记录集合</param>
/// <param name="left">起点位置</param>
/// <param name="right">终点位置</param>
private static void MergeSort<T>(T[] array, int left, int right) where T : IComparable<T>
{if (left >= right)return;int mid = (left + right) / 2;MergeSort(array, left, mid); //递归排序左边MergeSort(array, mid + 1, right); //递归排序右边Merge(array, left, mid, right); //合并
}/// <summary>
/// 合并排序
/// </summary>
/// <typeparam name="T">需要排序记录的类型</typeparam>
/// <param name="array">需要排序的记录集合</param>
public static void MergeSort<T>(T[] array) where T : IComparable<T>
{MergeSort(array, 0, array.Length - 1);
}

数据结构与算法:19 排序相关推荐

  1. 数据结构与算法(三) 排序算法(代码示例)

    数据结构与算法三 排序算法 1. 选择排序 2. 插入排序 3. 冒泡排序 4. 归并排序 5. 快速排序 6. 希尔排序 7. 堆排序 总结 1. 选择排序 选择排序的基本原理: 对于未排序的一组记 ...

  2. 在Object-C中学习数据结构与算法之排序算法

    笔者在学习数据结构与算法时,尝试着将排序算法以动画的形式呈现出来更加方便理解记忆,本文配合Demo 在Object-C中学习数据结构与算法之排序算法阅读更佳. 目录 选择排序 冒泡排序 插入排序 快速 ...

  3. 数据结构排序算法实验报告_[数据结构与算法系列]排序算法(二)

    我的上一篇文章向大家介绍了排序算法中的冒泡排序.插入排序和选择排序.它们都是平均时间复杂度为 O(n^2) 的排序算法,同时还为大家讲解了什么是原地排序和什么是排序的稳定性.下图是这三种算法的比较,不 ...

  4. 数据结构与算法之排序算法

    数据结构与算法之排序算法 排序算法的介绍 ​ 排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排序的过程. 排序的分类 1)内部排序:指将需要处理的数据都加载到内部 ...

  5. 常见数据结构和算法实现(排序/查找/数组/链表/栈/队列/树/递归/海量数据处理/图/位图/Java版数据结构)

    常见数据结构和算法实现(排序/查找/数组/链表/栈/队列/树/递归/海量数据处理/图/位图/Java版数据结构) 数据结构和算法作为程序员的基本功,一定得稳扎稳打的学习,我们常见的框架底层就是各类数据 ...

  6. python数据结构与算法之排序

    排序算法的稳定性: 假设有一串数据:(4,1)(3,1)(3,7)(5,6):要求按照第一个数排序,结果如下: 第一种:(3,1)(3,7)(4,1)(5,6)(3相同,维持原来的次序) 第二种:(3 ...

  7. 数据结构和算法之排序一:归并排序

    我们不得不承认一个事实,java学习过程中如果我们掌握了各种编程手段和工具,确实可以做一些开发,这就是一些培训机构敢告诉你几个月就能掌握一门语言的原因.但是随着时间的发展,我们总会感觉,这一类人如果不 ...

  8. 【数据结构与算法】排序优化

    冒泡.插入.选择 O(n^2) 基于比较 快排.归并 O(nlogn) 基于比较 计数.基数.桶 O(n) 不基于比较 总结:如何实现一个通用的高性能的排序函数? 一.如何选择合适的排序算法? 1.排 ...

  9. 【数据结构与算法】排序 冒泡、插入、选择 O(n^2)

    冒泡.插入.选择 O(n2) 基于比较 快排.归并 O(nlogn) 基于比较 计数.基数.桶 O(n) 不基于比较 一.如何分析一个排序算法? 学习排序算法的思路?明确原理.掌握实现以及分析性能. ...

  10. 数据结构与算法——列表排序(一篇文章带你了解排序算法)

    数据结构与算法基础 列表排序: 什么是列表排序? 排序:将一组"无序"的记录序列调整为"有序"的记录序列. 列表排序:将无序列表变为有序列表. 内置函数:sor ...

最新文章

  1. 【linux】串口编程(一)——配置串口
  2. python 数据分析学什么-python数据分析师要学什么
  3. 笔记-项目管理基础知识-项目信息(工作绩效信息、绩效数据、绩效报告)
  4. iis url重写 域名跳转子目录_逐浪CMS小哥整理IIS设置URL重写,实现页面的跳转的重定向方法...
  5. 操作多个表_4_查询不再另外一个表里的记录
  6. 01 c++常见面试题总结
  7. Android之Bitmap高效缓存以及android缓存策略
  8. spring 2.2 改进_Spring 4中@ControllerAdvice的改进
  9. python读取坐标文本文件_使用python读取txt坐标文件生成挖空矿山_探矿批量
  10. CodeForces1005D - Polycarp and Div 3
  11. IDEA 2020下载与安装
  12. Oracle创建临时表
  13. 搞了一个更完善的javaagent项目结构
  14. 各类木材强度_常用木材分类
  15. (专升本)PowerPoint(插入超链接和动作)
  16. 解读第一个C++程序
  17. 运维自动化之salt
  18. 23.2.7 点亮三个灯
  19. 紧凑存储的杜利特尔分解法Doolittle(LU分解法)_解线性方程组的直接解法
  20. ras私钥c#转java_C#和JAVA的RSA密钥、公钥转换

热门文章

  1. java用if语句调用方法_J2SE中main函数中的if语句想要调用另一个类的方法怎么能实现?...
  2. 一份整理 | PyTorch是什么,为何选择它
  3. 零基础怎么学习Java?
  4. 学python培训到底能干嘛
  5. php redis set集合操作,php对redis的set(集合)操作
  6. [转]MySQL修改时区的方法小结
  7. OpenGLES 关于 数学 的分支 - 线性变化量、离散量、随机量
  8. 小功能 - 收藏集 - 掘金
  9. Alipay秘钥问题
  10. HAProxy+Keepalived高可用负载均衡配置