数据结构--算法排序

有一段时间没有用各种排序算法了,以至于现在都有些混淆了,今天特地整理了一下关于数据结构中的各种算法:

1、冒泡排序

冒泡排序算法的原理如下:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。

  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。

  3. 针对所有的元素重复以上的步骤,除了最后一个。

  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

举个简单例子,现在要对   12    5     7      15     2      27 这组数据进行排序,根据冒泡排序的原理:

第一步:比较 12 和 5 ,12比5大,交换位置,即 5   12   7   15   2   27;

第二步:继续和后面相邻元素比较,最后一趟下来的结果是  5  7  12  2  15  27;

第三步:重复第一二步的步骤,最后的结果是:2  5  7  12  15  27  

2、插入排序

这里就先简单的介绍一下直接插入排序,直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。

直接插入排序的算法思路:

(1) 设置监视哨r[0],将待插入记录的值赋值给r[0];

(2) 设置开始查找的位置j;

(3) 在数组中进行搜索,搜索中将第j个记录后移,直至r[0].key≥r[j].key为止;

(4) 将r[0]插入r[j+1]的位置上。

例如,已知待排序的一组记录是:

60,71,49,11,24,3,66

假设在排序过程中,前3个记录已按关键码值递增的次序重新排列,构成一个有序序列:

49,60,71

将待排序记录中的第4个记录(即11)插入上述有序序列,以得到一个新的含4个记录的有序序列。首先,应找到11的插入位置,再进行插入。可以讲11放入数组的第一个单元r[0]中,这个单元称为监视哨,然后从71起从右到左查找,11小于71,将71右移一个位置,11小于60,又将60右移一个位置,11小于49,又再将49右移一个位置,这时再将11与r[0]的值比较,11≥r[0],它的插入位置就是r[1]。假设11大于第一个值r[1]。它的插入位置应该在r[1]和r[2]之间,由于60已经右移了,留出来的位置正好留给11.后面的记录依照同样的方法逐个插入到该有序序列中。若记录数n,续进行n-1趟排序,才能完成。

3、选择排序

选择排序也是一种简单直观的排序算法。它的工作原理很容易理解:初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。   

注意选择排序与冒泡排序的区别:冒泡排序通过依次交换相邻两个顺序不合法的元素位置,从而将当前最小(大)元素放到合适的位置;而选择排序每遍历一次都记住了当前最小(大)元素的位置,最后仅需一次交换操作即可将其放到合适的位置。

例如,现在对12    5     7      15     2      27 这组数据进行排序,我们需要设置一个变量k来记住当前最大或最小元素的位置,比如说示例中 2 的位置,这只是第一次的结果,同理,我们后面要依次找到5  7  12  15  27的位置,最后对他们的位置进行交换,得到最终排序结果。

function selectionSort(arr) {
    var len = arr.length;
    var minIndex, temp;
    for (var i = 0; i < len - 1; i++) {
        minIndex = i;                         //每次都从已排序序列的末尾后一位开始
        for (var j = i + 1; j < len; j++) {
            if (arr[j] < arr[minIndex]) {     //寻找最小的数
                minIndex = j;                 //将最小数的索引保存
            }
        }
        
       //相对于冒泡排序,只进行了一次交换
        temp = arr[i];  
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
    return arr;
}

4、快速排序

快速排序的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

一趟快速排序的算法是:

1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;

2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];

3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]互换;

4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;

5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。

假设用户输入了如下数组:

下标

0

1

2

3

4

5

数据

6

2

7

3

8

9

创建变量i=0(指向第一个数据), j=5(指向最后一个数据), k=6(赋值为第一个数据的值)。

我们要把所有比k小的数移动到k的左面,所以我们可以开始寻找比6小的数,从j开始,从右往左找,不断递减变量j的值,我们找到第一个下标3的数据比6小,于是把数据3移到下标0的位置,把下标0的数据6移到下标3,完成第一次比较:

下标

0

1

2

3

4

5

数据

3

2

7

6

8

9

i=0 j=3 k=6

接着,开始第二次比较,这次要变成找比k大的了,而且要从前往后找了。递加变量i,发现下标2的数据是第一个比k大的,于是用下标2的数据7和j指向的下标3的数据的6做交换,数据状态变成下表:

下标

0

1

2

3

4

5

数据

3

2

6

7

8

9

i=2 j=3 k=6

称上面两次比较为一个循环。

接着,再递减变量j,不断重复进行上面的循环比较。

在本例中,我们进行一次循环,就发现i和j“碰头”了:他们都指向了下标2。于是,第一遍比较结束。得到结果如下,凡是k(=6)左边的数都比它小,凡是k右边的数都比它大:

下标

0

1

2

3

4

5

数据

3

2

6

7

8

9

如果i和j没有碰头的话,就递加i找大的,还没有,就再递减j找小的,如此反复,不断循环。注意判断和寻找是同时进行的。

然后,对k两边的数据,再分组分别进行上述的过程,直到不能再分组为止。

注意:第一遍快速排序不会直接得到最终结果,只会把比k大和比k小的数分到k的两边。为了得到最后结果,需要再次对下标2两边的数组分别执行此步骤,然后再分解数组,直到数组不能再分解为止(只有一个数据),才能得到正确结果。

5、希尔排序

希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。

一般的初次取序列的一半为增量,以后每次减半,直到增量为1。

给定实例的shell排序的排序过程

假设待排序文件有10个记录,其关键字分别是:

49,38,65,97,76,13,27,49,55,04。

增量序列的取值依次为:

5,2,1

希尔排序

简单点说,也就是:

第一趟,增量为5:{49,13},{38,27},{65,49},{97,55},{76,04}

排序后:{13,49},{27,38},{49,65},{55,97},{04,76}

结果是:13  27  49  55  04  49  38  65  97  76

第二趟,增量为2:{13,49,04,38,97},{27,55,49,65,76}

排序后:{04,13,38,49,97},{27,49,55,65,76}

结果是:04,27,13,49,38 ,55,49,65,97,76

第三趟,增量为1:{04,27,13,49,38 ,55,49,65,97,76}

排序后:{04,14,27,38,49,49,55,65,76,97}

结果是:04,14,27,38,49,49,55,65,76,97

6、归并排序

归并排序其实是分治法(Divide and Conquer)的典型应用。其主要算法思想将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个有序的子序列,再把有序的子序列合并为整体有序序列。

如 设有数列{6,202,100,301,38,8,1}

初始状态:6,202,100,301,38,8,1

第一次归并后:{6,202},{100,301},{8,38},{1},比较次数:3;

第二次归并后:{6,100,202,301},{1,8,38},比较次数:4;

第三次归并后:{1,6,8,38,100,202,301},比较次数:4;

总的比较次数为:3+4+4=11;

7、拓扑排序

拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。

由AOV网构造拓扑序列的拓扑排序算法主要是循环执行以下两步,直到不存在入度为0的顶点为止。

(1) 选择一个入度为0的顶点并输出之;

(2) 从网中删除此顶点及所有出边。

循环结束后,若输出的顶点数小于网中的顶点数,则输出“有回路”信息,否则输出的顶点序列就是一种拓扑序列。

例如:

上图是一个DAG图,即有向无环图,那么,如何写出它的拓扑排序呢?

结合我们上面讲到的两个步骤:(1)找度为0的节点;(2)删除该节点所有相关联的边

具体过程如下:

于是,得到的拓扑排序结果是{ 1,2,4,3,5 }

8、堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。

堆分为大根堆和小根堆,是完全二叉树。

大根堆,顾名思义,就是所有的根节点的值都比左右节点值大;小根堆,则是根节点的值比其左右节点值小。

                                

                                  大顶堆                                                                                                       小顶堆

堆排序的步骤:

(1) 构建大根堆。首先我们的原始数组一般情况下是不满足堆的条件,既然我们要可用大根堆的性质进行排序,第一步当然是对原始数组进行处理,构建大根堆。

 (2) 根节点数据处理以及大根堆重构。

举个简单的例子,现在有一个无序的序列A = { 5,4,17,13,15,12,10 },其下标为{ 1,2, 3, 4,5 ,6 ,7 }对其进行堆排序:

要构建大根堆,只需要遍历所有非叶子节点元素,使其所有的叶子节点均不大于足该节点元素即可。构建大根堆自底向上对二叉树进行遍历,如果发现父节点的值小于子节点的值,则将父节点的值与子节点的最大值进行交换,保证父节点的数据总是大于子节点的数据。如果发生了数据的交换,有可能令子节点不满足大根堆条件,需要进行会输重构大根堆。

猜你喜欢

转载自blog.csdn.net/weixin_38629529/article/details/82820869