排序算法总结一

排序算法包括插入排序、选择排序、冒泡排序、快速排序、归并排序以及基数排序等六种,下面我们将从他们各自的原理、实现、时间与空间复杂度以及稳定性等方面进行分析。

1. 插入排序

基本思想:将一个记录插入到已排序好的有序表中,从而得到一个新的记录数增1的有序表。当表中只有一个数时当然是有序的,因此我们从第二的数开始实现插入算法。我们先看一下实现代码:

 1 void InsertSort(vector<int>& a)
 2 {
 3     int n = a.size();
 4     int i, j;
 5     for (i = 1; i < n; i++)
 6     if (a[i] < a[i - 1])
 7     {
 8         int temp = a[i];
 9         for (j = i - 1; j >= 0 && a[j] > temp; j--)
10             a[j + 1] = a[j];
11         a[j + 1] = temp;
12     }
13 }

该过程实际上跟打扑克牌时摸排后的插牌过程是一个故事(当然哪些不安常理插排的人除外),如果我们给a初始化:a{5,4,8,7,3}.这好比你得到先后五张牌: 5 、 4 、8、7、3.

第5行中的循环 第一次i = 1 ,表示当你摸到第二张牌时开始考虑它的插入问题,很显然你要将它插到5之前,即满足第6行的判断条件。那要将其插到哪个,我们可以将其与它的前一个数比较,如果比前一个数小就将前一个数向后移动一位,为它的插入留出空间,同理然后继续与前前一个比较。那到什么时候结束呢?第9行中的判断条件:直到前一个数大于或等于它,又或者前面已没有数。在这里即j==-1时循环跳出,最后将新纪录数插入,见第11行,4被插到5的前面。当然为了避免插入数被前一个数移动后覆盖,要先将其保存在一个变量中,见第8行。

接着继续循环 i = 2,因为8比5大不满足第6行的条件,即8在原地不移动,即直接插到5的后面。继续循环,7要比8小满足第6行条件,将8向后移动一位,7继续与前一位比较,因为7比5小,所以内部循环结束,将7插到5的后面。继续循环,3比它前面所有数都小,最后内部循环跳出条件为j ==-1,因此将3插到第一位,到此整个过程结束,得到有序表{3,4,5,7,8}.

下面分析该算法的时间与空间复杂度:

因为整个过程只使用了一个临时变量保存被插入值,空间复杂度为O(1).

当最好的情况,给定的是有序表,则每次循环都不会满足第6行的条件,因此没有发生移动,只进行了n-1次比较,时间复杂度为O(n)。

因此对于一些本身就基本有序的和数据量比较小的,使用插入排序比较有优势。

当最坏的情况,给定的是逆序表,则每次都将它前面的所有元素向后移动一位,移动次数为1+2+...+n-1=(n-1)n/2,比较次数为2+3+...+n = (n-1)(n+2)/2. 如果排序是随机的,则平均的比较和移动次数为n^2/4, 因此时间复杂度为O(n^2).

从上面的比较过程中看出,碰见一个和插入元素相等的数时,直接把插入元素插入该数的后面。所以,相等元素的前后顺序没有改变,所以插入排序是稳定的。

在上面提到当记录本身就基本有序的和数据量比较小时,插入排序效率高,当这个条件很难满足,为了创造这样的条件,将相距某个增量的记录组成一个子序列,将每个子序列实现插入排序,这便是希尔排序。

 1 void ShellSort(vector<int>& a)
 2 {
 3     int n = a.size();
 4     int i, step;
 5     for (step = n / 2; step > 0; step /= 2)
 6     {
 7         for (i = step; i < n; i++)
 8         {
 9             if (a[i - step]>a[i])
10             {
11                 int temp = a[i];
12                 int k = i - step;
13                 while (k >= 0 && a[k] > temp)
14                 {
15                     a[k + step] = a[k];
16                     k -= step;
17                 }
18                 a[k + step] = temp;
19             }
20         }
21     }
22 }

从第7行到第18行,这整个过程基本上就是一个插入排序,区别是之前是与前一个数比较,而这里是与子序列的前一个数比较,这子序列相距step,即前step个数比较,完成子序列的排序。每次插入排序完成后,便减小子序列的距离,一直到减为1,此时便是一个标准的插入排序。我们给定一个记录a{49,38,65,97,76,13,27,49,55,4},第一次我们使用的距离step = 10/2 = 5,子序列为{49,13}、{38,27}

{65,49}、{97,55}、{76,4},经过一次排序后得到{13,27,49,55,4,49,38,65,97,76};第二次使用距离step = 5 /2 =2,子序列为

{13,49,4,38,97}、{27,55,49,65,76},排序后得到{4,27,13,49,38,55,49,65,97,76},最后一次距离step = 2/2=1,此时成为一般的插入排序。

希尔排序的目的就是在数据量比较小或记录本身就基本有序的条件下进行插入排序,这种情况下的插入排序效率高,当step较大时,每个子序列数量小,二当step变大每个子序列数量变大后,经过之前的子序列排序,记录已满足基本有序的条件。希尔排序的时间复杂度与step 的选取有关,它的选取有多种方法,目前还没找到最佳方法。因为希尔排序有跳跃的调整数据顺序,因此它是不稳定的。

2. 选择排序

基本思想:首先从第一个数开始,将它与其他所有的数比较,找到最小的数将其放到第一个位置;然后再从第二个数开始,将它与剩下所有的数比较,找到最小的放到第二个位置,以此类推,最终将所有的数排好序。

 1 void SelectSort(vector<int>& a)
 2 {
 3     int n = a.size();
 4     int i, j, nMinIndex;
 5     for (i = 0; i < n; i++)
 6     {
 7         nMinIndex = i; //找最小元素的位置
 8         for (j = i + 1; j < n; j++)
 9         if (a[j] < a[nMinIndex])
10             nMinIndex = j;
11         swap(a[i], a[nMinIndex]); //将这个元素放到无序区的开头
12     }
13 }

该算法过程比较简单,只需用一个变量记录最小元素的位置,空间复杂度为O(1).每次循环找到无序区的最小元素并将其放到无序区的开头位置。该算法的比较次数给定序列无关,都是 n-1+n-2+...+1=n(n-1)/2次,移动次数当为顺序时为0,为逆序时为n-1.所有该算法的时间复杂度为O(n^2),并且该算法是稳定的。

上述过程中每次循环只能确定一个值的位置,我们可以同时确定最大和最小值的位置,使得循环的次数减半。

 1 void SelectSort1(vector<int>& a)
 2 {
 3     int n = a.size();
 4     int i, j, nMinIndex,nMaxIndex;
 5     for (i = 0; i <= n/2; i++)
 6     {
 7         nMinIndex = i; nMaxIndex = i;  //找最小,最大元素的位置
 8         for (j = i + 1; j < n-i; j++)
 9         {
10             if (a[j] < a[nMinIndex])
11             {
12                 nMinIndex = j;
13                 continue;
14             }
15             if (a[j]>a[nMaxIndex])
16                 nMaxIndex = j;
17         }
18         if (i == nMaxIndex)
19         {
20             Exchange(a[n - 1 - i], a[nMaxIndex]);
21             Exchange(a[i], a[nMinIndex]);
22         }
23         else if (i == nMinIndex)
24         {
25             Exchange(a[n - 1 - i], a[nMaxIndex]);
26         }
27         else
28         {
29             Exchange(a[i], a[nMinIndex]);
30             Exchange(a[n - 1 - i], a[nMaxIndex]);
31         }
32     }
33 }

在这里注意不能将最小和最大值与无序列中的最前和最后位置进行交换,首先要判断最大值与最小值的位置是否与当前位置相同,如果最大值位置就是当前位置,只能先将最大值与最末位置元素交换,再将最小元素与当前位置交换,如果先交换最小元素与当前元素,进行最大值交换时又会将最小元素移动到最后位置。如果最小值位置就是当前位置,我们只需要交换最大值与最后一个元素即可。其他情况不用在意交换的先后。

选择排序中每一轮比较只选出了最小记录,而没有将比较过的结果记录下来,这样每一轮的比较可能会出现重复的情况,如果能将每次比较的结果记录下来,则能大大提高时间复杂度,这里使用堆排序。堆是拥有下列性质的完全二叉树:每个节点的值都大于或等于左右孩子节点的值,称为大顶堆;每个节点的值都小于或等于左右孩子节点的值,称为小顶堆;它可以由如下公式表示:

堆排序的思想:将待排序的序列构成一个大顶堆(也可以使用小顶堆,原理一样),将根节点与序列的最后位置的元素交换,此时便将最大值移到最后了;接着将剩余的n-1个元素再构成一个大顶堆,这样便得到第二大的元素。如此反复,最后得到一个有序的序列。

接下来的问题是如何构造堆,要保证每一个节点都满足堆的性质,从最后一个节点进行调整,然后反复进行此过程:

(1) n 个结点的完全二叉树,则最后一个结点是第个结点的子树。因此从第个结点为根的子树开始,该子树成为堆。

(2) 接着向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。代码如下:

void Maxheap(vector<int>& a, int i)
{
    int n = a.size();
    int j, temp;
    temp = a[i];
    j = 2 * i + 1;
    while (j < n)
    {
        if (j + 1 < n && a[j + 1] > a[j]) //在左右孩子中找最大的
            j++;

        if (a[j]<=temp)
            break;

        a[i] = a[j];     //把较大的子结点往上移动,替换它的父结点
        i = j;
        j = 2 * i + 1;
    }
    a[i] = temp;
}
void BuildHeap(vector<int>& a)
{
    int r = a.size();
    for (int i =r/2-1; i>=0; i--)
        Maxheap(a, i);
}

建立堆之后,序列的第一个元素为最大值,将其与最后的元素交换,然后将其pop出序列,接着对序列中剩余的元素建堆。循环操作直到序列中只有一个元素,代码如下:

 1 vector<int> HeapSort(vector<int>& a)
 2 {
 3     BuildHeap(a);
 4     int length = a.size();
 5     vector<int> res(length, 0);
 6     for (int i = length-1; i>=1; i--)
 7     {
 8         Exchange(a[i], a[0]);
 9         res[i] = a[i];
10         a.pop_back();
11         Maxheap(a, 0);
12     }
13     res[0]=a[0];
14     return res;
15 }
时间: 2024-08-28 10:03:38

排序算法总结一的相关文章

经典排序算法 - 冒泡排序Bubble sort

 原文出自于 http://www.cnblogs.com/kkun/archive/2011/11/23/bubble_sort.html 经典排序算法 - 冒泡排序Bubble sort 原理是临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换, 这样一趟过去后,最大或最小的数字被交换到了最后一位, 然后再从头开始进行两两比较交换,直到倒数第二位时结束,其余类似看例子 例子为从小到大排序, 原始待排序数组| 6 | 2 | 4 | 1 | 5 | 9 | 第一趟排序(外循环) 第

排序算法比较及其应用

一.将各种数据排序 只要实现了Comparable接口的数据类型就可以被排序. 但要使算法能够灵活地用不同字段进行排序,则是后续需要考虑的问题. 1.指针排序 在Java中,指针操作是隐式的,排序算法操作的总是数据引用,而不是数据本身. 2.键不可变 如果在排序后,用例还可以改变键值,那么数组很可能就不是有序的了.类似,优先队列也会乱套. Java中,可以用不可变数据类型作为键来避免这个问题,如String,Integer,Double和File都是不可变的. 3.廉价交换 使用引用的另一个好处

选择排序 —— 排序算法系列

假设我们有如下一个数组: 使用选择排序算法对这个数组进行排序,步骤如下: 第 1 次 在下标0到6之间找到最小的数字,我们可以发现最小的数字是15,它在下标为4的位置上: 把下标4上面的数字跟下标0上面的数字互换,得到排序如下图的数组: 第 2 次 在下标1到6之间找到最小的数字,我们可以发现最小的数字是33,它在下标为5的位置上: 把下标5上面的数字跟下标1上面的数字互换,得到排序如下图的数组: 第 3 次 在下标2到6之间找到最小的数字,我们可以发现最小的数字是48,它在下标为5的位置上:

排序算法Java版,以及各自的复杂度,以及由堆排序产生的top K问题

常用的排序算法包括: 冒泡排序:每次在无序队列里将相邻两个数依次进行比较,将小数调换到前面, 逐次比较,直至将最大的数移到最后.最将剩下的N-1个数继续比较,将次大数移至倒数第二.依此规律,直至比较结束.时间复杂度:O(n^2) 选择排序:每次在无序队列中"选择"出最大值,放到有序队列的最后,并从无序队列中去除该值(具体实现略有区别).时间复杂度:O(n^2) 直接插入排序:始终定义第一个元素为有序的,将元素逐个插入到有序排列之中,其特点是要不断的 移动数据,空出一个适当的位置,把待插

排序算法总结

各种排序算法总结  排序算法  插入排序 冒泡排序  选择排序  归并排序  快速排序 堆排序  计数排序  基数排序  桶排序  思想  构建有序序列 两两交换 每次找一个最小值 分治法思想 分治法思想 最小堆.最大堆 数字本身的属性  对数据选择多种基数  函数的映射关系.Hash  数据结构  数组  数组  数组  数组 不定   数组 数组 数组  数组  最差时间复杂度 O(n^2)   O(n^2)   O(n^2)   O(n*lgn)  O(n^2).改进O(n*lgn)  O

七大常见排序算法总结

文档版本 开发工具 测试平台 工程名字 日期 作者 备注 V1.0 2016.04.06 lutianfei none V1.1 2016.07.16 lutianfei 增加了归并排序说明 V2.0 2016.07.19 lutianfei 完善了排序算法的总结 排序另一种分法 外排序:需要在内外存之间多次交换数据才能进行 内排序: 插入类排序 直接插入排序 希尔排序 选择类排序 简单选择排序 堆排序 交换类排序 冒泡排序 快速排序 归并类排序 归并排序 排序方法 平均情况 最好情况 最坏情况

数据结构——各排序算法的比较

1.从时间复杂度比较  从平均时间复杂度来考虑,直接插入排序.冒泡排序.直接选择排序是三种简单的排序方法,时间复杂度都为O(n2),而快速排序.堆排序.二路归并排序的时间复杂度都为O(nlog2n),希尔排序的复杂度介于这两者之间.若从最好的时间复杂度考虑,则直接插入排序和冒泡排序的时间复杂度最好,为O(n),其它的最好情形同平均情形相同.若从最坏的时间复杂度考虑,则快速排序的为O(n2),直接插入排序.冒泡排序.希尔排序同平均情形相同,但系数大约增加一倍,所以运行速度将降低一半,最坏情形对直接

八种排序算法

最近一段时间自己在研究各种排序算法,于是自己写了一个八种排序算法的集合: /************************************************************************* > Copyright (c)2014 stay hungry,stay foolish !!! > File Name: sort.cpp > Author: kanty > Mail: [email protected] > Created Time:

排序算法 之 快速排序

快速排序是基于分治思想的一种排序算法,就像该方法的名字一样,速度比较快,所以叫做快速排序:它的平均时间复杂度为O(N*logN),最坏时间复杂度为O(n2),由于快速排序在序列元素数量多的时候速度比较快,所以很多语言内置的排序方法也是用快速排序实现的.快速排序也有很多优化的版本,比如在排序时基数的选择等等-下面就说一下一般的快速排序的实现. 基本思想: 快速排序的基本思想就是,先从待排序的序列中任选一个元素作为基数,然后将序列中的其他小于基数的元素放在基数的左边,大于或等于基数的元素放在基数的右

排序算法的JS实现

排序算法是基础算法,虽然关键在于算法的思想而不是语言,但还是决定借助算法可视化工具结合自己常用的语言实现一下 1.冒泡排序 基本思路:依次比较两两相邻的两个数,前面数比后面数小,不变.前面数比后面数大,交换顺序.一轮下来,最后的一个数是最大的数. 外循环每增加一次,内循环减少一次. 图形展示: function bubbleSort(arr){ for (var i = 0; i < arr.length; i++) { for (var j = 0; j< arr.length-i-1; j