数据结构与算法:排序

排序问题一直是计算机技术研究的重要问题,排序算法的好坏直接影响程序的执行速度和辅助存储空间的占有量,所以各大IT企业在笔试面试中也经常出现有关排序的题目。本节详细分析常见的各种排序算法,并从时间复杂度、空间复杂度、适用情况等多个方面对它们进行综合比较。

  • 选择排序
  • 插入排序
  • 冒泡排序
    • 双向冒泡排序
  • 如何进行归并排序
  • 快速排序
  • 希尔排序
  • 堆排序
  • 各种排序算法有什么优劣

选择排序

选择排序是一种简单直观的排序算法,基本原理如下:对于给定的一组记录,经过第一轮比较后得到最小的记录,然后将该记录与第一个记录的位置进行交换;接着对不包括第一个记录以外的其他记录进行第二轮比较,得到最小的记录并与第二个记录进行位置交换;重复该过程,直到进行比较的记录只有一个时为止。

从简单选择排序的过程来看,它的特点就是交换移动数据次数相当少,这样也就节约了相应的时间。无论是最好情况,还是最差情况,其比较次数都是一样的,第i趟排序需要进行n-i次。而对于交换次数而言,最好的情况是有序,需要交换0次;最差的情况,即逆序时,交换次数为n-1次,基于最终的排序时间是比较与交换的次数总和,因此总的时间复杂度依然为O(n^2)。

插入排序

对于给定的一组记录,初始时假设第一个记录自成一个有序序列,其余的记录为无序序列;接着从第二个记录开始,按照记录的大小依次将当前处理的记录插入到其之前的有序序列中,直至最后一个记录插入到有序序列中为止。

冒泡排序

冒泡排序顾名思义就是整个过程像气泡一样往上升,单向冒泡排序的基本思想是(假设由小到大排序):对于给定的n个记录,从第一个记录开始依次对相邻的两个记录进行比较,当前面的记录大于后面的记录时,交换其位置,进行一轮比较和换位后,n个记录中的最大记录将位于第n位;然后对前(n-1)个记录进行第二轮比较;重复该过程直到进行比较的记录只剩下一个时为止。

双向冒泡排序

双向冒泡排序是冒泡排序的一种优化,基本思想是首先将第一个记录的关键字和第二个记录的关键字进行比较,若为‘逆序’(即L.r[1].key > L.r[2].key),则将两个记录交换,然后比较第二个记录和第三个记录的关键字。依次类推,直至第n-1个记录的关键字和第n个记录的关键字比较过为止。这是第一趟冒泡排序,其结果是使得关键字最大的记录被安置到最后一个记录的位置上。

第一趟排序之后进行第二趟冒泡排序,将第n-2个记录的关键字和第n-1个记录的关键字进行比较,若为“逆序”(即 L.r[n-1].key

#include<iostream>

using namespace std;

void Swap( int &a, int &b )
{
    int temp = a;
    a = b;
    b = temp;
}
void Bubble2Sort( int arr[], int length )
{
    int left = 1;
    int right = length - 1;
    int t;
    do
    {
        // forward
        for(int i=right; i>=left; i-- )
        {
            if( arr[i]<arr[i-1] )
            {
                Swap( arr[i],arr[i-1] );
                t = i;
            }
        }
        left = t + 1;
        //...
        for( int i=left; i<right+1; i++ )
        {
            if(arr[i]<arr[i-1])
            {
                Swap( arr[i], arr[i-1]);
                t = i;
            }
        }
        right = t-1;
    }while( left<=right );
}

int main()
{
    int i=0;
    int a[] = { 5,4,9,8,7,6,0,1,3,2 };
    int len = sizeof(a) / sizeof(a[0]);
    Bubble2Sort( a,len );
    for( i=0; i<len; i++ )
        cout << a[i] << "   ";
    cout << endl;

    return 0;
}

如何进行归并排序

归并排序是利用递归与分治技术将数据序列划分成为越来越小的半子表,再对半子表排序,最后再用递归步骤将排好序的半子表合并成为越来越大的有序序列。其中“归”代表的是递归的意思,即递归地将数组折半地分离为单个数组。例如,数组 [ 2,6,1,0 ] 会先折半,分为[ 2,6] 和【 1,0 】两个子数组,然后再折半将数组分离,分为【2】【6】和【1】【0】。“并”就是将分开的数据按照从小到大或者从大到小的顺序在放到一个数组中。如上面的【2】、【6】合并到一个数组中是【2,6】,【1】【0】合并到一个数组中是【0,1】,然后再将【2,6】和【0,1】合并到一个数组中即为【0,1,2,6】。

具体而言,归并排序算法的原理如下:对于给定的一组记录(假设共有n个记录),首先将每两个相邻的长度为1的子序列进行归并,得到 n/2(向上取整)个长度为2或1的有序子序列,再将其两两归并,反复执行此过程,直到得到一个有序序列为止。

所以,归并排序的关键就是两步:第一步,划分子表;第二步,合并半子表。

以数组{ 49,38,65,97,76,13,27 } 为例,排序过程如下:

#include<iostream>

using namespace std;

void Merge( int arr[], int p,int q, int r )
{
    int i,j,k,n1,n2;
    n1 = q-p+1;
    n2 = r - q ;
    int *L = new int[n1];
    int *R = new int[n2];
    for( i=0, k=p; i<n1; i++,k++ )
        L[i] = arr[k];

    for( i=0,k=q+1; i<n2; i++,k++ )
        R[i] = arr[k];
    for( k=p,i=0,j=0; i<n1 && j<n2; k++ )
    {
        if(L[i] >R[j])
        {
            arr[k] = L[i];
            i++;
        }
        else
        {
            arr[k] = R[j];
            j++;
        }
    }
    if( i<n1 )
    {
        for( j=i; j<n1; j++,k++ )
            arr[k] = L[j];
    }
    if( j<n2 )
    {
        for(i=j; i<n2; i++,k++ )
            arr[k] = R[i];
    }
}

void MergeSort( int arr[], int p, int r )
{
    if( p<r )
    {
        int q = ( p+r )/2;
        MergeSort( arr, p, q );
        MergeSort( arr, q+1,r );
        Merge( arr, p,q,r );
    }
}
int main()
{
    int a[] = { 5,4,9,8,7,6,0,1,3,2 };
    int len = sizeof( a )/sizeof( a[0] );

    MergeSort( a,0,len-1 );
    for( int i=0; i<len; i++ )
        cout << a[i] << "  ";
    cout << endl;
    return 0;
}

二路归并排序的过程需要进行logn趟。每一趟归并排序的操作,就是将两个有序子序列进行归并,而每一对有序子序列归并时,记录的比较次数均小于等于记录的移动次数,记录移动的次数均等于文件中记录的个数n,即每一趟归并的时间复杂度为O(n)。因此,二路归并排序的时间复杂度为O(nlogn)。

快速排序

快速排序是一种非常高效的排序算法,它采用“分而治之”的思想,把大的拆分为小的,小的再拆分为更小的。其原理是:对于一组给定的记录,通过一趟排序后,将原序列分为两部分,其中前部分的所有记录均比后部分的所有记录小,然后再依次对前后两部分的记录进行快速排序,递归该过程,直到序列中的所有记录均有序为止。

具体算法步骤如下。

(1)分解:将输入序列arr【m,…,n】划分为两个非空子序列arr【m,…,k】和arr【k+1,…,n】,使arr【m,…,k】中任一元素的值不大于arr【k+1,…,n】中任一元素的值。

(2)递归求解:通过递归调用快速排序算法分别对划分的两个子序列进行排序。

(3)合并:由于对分解出的两个子序列的排序是就地进行的,所以在两个子序列都排序好后,不需要执行任何计算就已排好序。

以数组 { 49,38,65,97,76,13,27,49 } 为例

当初始的序列整体或局部有序时,快速排序额性能将会下降,此时快速排序将退化为冒泡排序。

快速排序的相关特点如下:

(1)最坏时间复杂度

最坏情况是指每次区间划分的结果都是基准关键字的左边(或右边)序列为空,而另一边的区间中的记录项仅比排序前少了一项,即选择的基准关键字是待排序的所有记录中最小或者最大的。例如,若选取第一个记录为基准关键字,当初始序列按递增顺序排序时,每次选择的基准关键字都是所有记录中的最小值,这时记录与基准关键字的比较次数会增多。因此,在这种情况下,需要进行(n-1)次区间划分。对于第k(0<k<n)次区间划分,划分前的序列长度为(n-k+1),需要进行(n-k)次记录的比较。当k从1~(n-1)时,进行的比较次数总共为 n(n-1)/2,所以在最坏情况下快速排序的时间复杂度为O(n^2)

(2)最好时间复杂度

最好情况是指每次区间划分的结果都是基准关键字左右两边的序列长度相等或者相差为1,即选择的基准关键字为待排序的记录中的中间值。此时,进行的比较次数总共为nlogn,所以在最好情况下快速排序的时间复杂度为O(nlogn)

(3)平均时间复杂度

快速排序的平均时间复杂度为O(nlogn)。虽然快速排序在最坏情况下的时间复杂度为O(n^2),但是在所有平均时间复杂度为O(nlogn)的算法中,快速排序的平均性能是最好的。

(4)空间复杂度

快速排序的过程中需要一个栈空间来实现递归。当每次对区间的划分都比较均匀时(即最好情况),递归树的最大深度为logn(向上取整)+1;当每次区间划分都使得有一边的序列长度为0时(最好情况),递归树的最大深度为n。在每轮排序结束后比较基准关键字左右的记录个数,对记录多的一边先进行排序,此时,栈的最大深度可降为logn。因此,快速排序的平均空间复杂度为O(logn)

(5)基准关键字的选取

基准关键字的选择是决定快速排序算法性能的关键。常用的基准关键字的选择有以下几种方式

  • 三者取中

    三者取中是指在当前序列中,将其首尾和中间位置上的记录进行比较,选择三者的中值作为基准关键字,在划分开始前交换序列中的第一个记录与基准关键字的位置。

  • 取随机数

    取left和right之间的一个随机数m,m作为基准关键字的位置

    希尔排序

    希尔排序也称为“缩小增量排序”。基本原理是:首先将待排序的元素分成多个子序列,使得每个子序列的元素个数相对较少,对各个子序列分别进行直接插入排序,待整个待排序列“基本有序”,再对所有元素进行一次直接插入排序。

希尔排序的关键并不是随便地分组后各自排序,而是将相隔某个“增量”的记录组成一个子序列,实现跳跃式的移动,使得排序的效率提高。

堆排序

堆是一种特殊的树形数据结构,其每个节点都有一个值,通常提到的堆都是指一棵完全二叉树,根节点的值小于(或大于)两个子节点的值,同时根节点的两个子树也分别是一个堆

堆排序是一树形选择排序,在排序过程中,将R[ 1,…,N ]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲节点和孩子节点之间的内在关系来选择最小的元素。

堆一般分为大顶堆和小顶堆两种不同的类型。对于给定的n个记录的序列(r(1),r(2),…,r(n)),当且仅当满足条件 (r(i)>=r(2i), i=1,2,…,n)时称之为大顶堆,此时堆顶元素比为最大值。 反之则为小顶堆,此时堆顶元素为最小值.

各种排序算法有什么优劣

时间: 2024-10-11 11:58:03

数据结构与算法:排序的相关文章

数据结构与算法-排序算法-partial

前言 都什么时代了,还写排序算法的总结? 原因有二.一是别人的精彩永远是别人的,你只有鼓掌的份儿:有些事情实际动手去做了才会有所体会. 二是排序算法是一类基础类的算法,不光是IT从业者真正入门的门槛,也是一些高级算法的关键部分或算法评估的benchmark. 计划说明的算法内容有哪些?  算法的思想.Java代码实现和平均算法复杂度.算法运行完整示例. 参考文献有哪些? wiki[EB/OL] Shaffer C. A. Data Structure and Algorithm Analysis

数据结构与算法 - 排序与搜索

排序与搜索 排序算法(英语:Sorting algorithm)是一种能将一串数据依照特定顺序进行排列的一种算法. 1.冒泡排序 冒泡排序(英语:Bubble Sort)是一种简单的排序算法.它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成.这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端. 冒泡排序算法的运作如下: 比较相邻的元素.如果第一个比第二个大(升序),就

数据结构和算法-排序算法-冒泡排序

##################     排序算法        ###################### """ 排序算法, 我们想要把线性表中的无序序列,排成有序序列,的算法,就是排序算法, 排序算法的稳定性 举例:假设对下面的元组要以他们的第一个数字来排序. (4, 1) (3, 1) (3, 7)(5, 6) 如果你排序之后,(3, 1) (3, 7)和原来的顺序一样,就是稳定的,否则就是不稳定的, (3, 1) (3, 7) (4, 1) (5, 6) (维

数据结构与算法-排序与查找(java描述)

在软件开发中,有两个常见的任务,一个是某一组中查找某个特定的元素,另一个是将某一组元素按照特定的顺序排序.我们可以使用多种算法来完成这些任务,而这些算法的差异也是值得我们去仔细研究的,接下来我们探究一下这些算法. 一.查找 1.1.线性查找法 线性查找就是通过索引对数组data的每个元素进行遍历,如果发现要找的目标元素和数组data中的某个元素相同时,就返回已经查找到,当然,我们也是可以改进一下,就是直接把找到的元素返回即可. public static <T extends Comparabl

数据结构与算法——排序算法

常见排序算法主要有: 插入排序(直接插入排序.希尔排序) 选择排序(直接选择排序.堆排序) 交换排序(冒泡排序.快速排序) 归并排序 基数排序 外部排序 一.直接插入排序 算法思想:在一个待排序列中,从第二个元素开始,依次进行排序,每次都将待排序元素从后往前,依次与前面的元素进行比较,从而将带排序元素移动到一个合适的位置,直到最后一个待排序元素移动到合适位置,则排序完成. 算法特点:最好情况下时间复杂度O(n),最坏情况下时间复杂度O(n2),稳定排序算法 二.希尔排序 希尔排序算法基础:待排序

[数据结构与算法]排序算法(Python)

1.直接插入排序 给定一个数组后,从第二个元素开始,如果比第一个小,就跟他交换位置,否则不动:第三个元素如果比第二个小,把第三个跟第二个交换位置,在把第二个与第一个比较:..... def insert_sort(arr): length = len(arr) for i in range(1,length): if arr[i] < arr[i-1]: for j in range(i-1,-1,-1): if arr[j+1] < arr[j]: arr[j+1],arr[j] = arr

Python数据结构与算法—排序和查找

排序和查找 排序(Sort)是将无序的记录序列(或称文件)调整成有序的序列. 常见排序方法: 冒泡排序 冒泡排序是一种简单的排序算法.它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成. 1 # 冒泡 2 def bubble(list_): 3 # 外层循环表达比较多少轮 4 for i in range(len(list_) - 1): 5 #内层循环把控比较次数 6 for j in r

数据结构与算法---排序算法(Sort Algorithm)

排序算法的介绍 排序也称排序算法 (Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程. 排序的分类 1) 内部排序: 指将需要处理的所有数据都加载 到内部存储器(内存)中进行排序. 2) 外部排序法:数据量过大,无法全部加载到内 存中,需要借助外部存储(文件等)进行 排序. 常见的排序算法分类 算法的时间复杂度 度量一个程序(算法)执行时间的两种方法 1.事后统计的方法这种方法可行, 但是有两个问题:一是要想对设计的算法的运行性能进行评测,需要实际运行该程序: 二是所

数据结构和算法-排序算法-快速排序

##################     快速排序        ####################### """ 快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为较小和较大的2个子序列,然后递归地排序两个子序列. 步骤为: 1,挑选基准值:从数列中挑出一个元素,称为"基准"(pivot); 2,分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以

数据结构和算法-排序算法-插入排序

##################    插入排序        #################### """ 插入算法: alist = [54,26,93,17,77,31,44,55,20] 还是把序列分为两部分, 一开始就把第一个数字认为是有序的, alist = [54, 26,93,17,77,31,44,55,20] 第一轮, 把第二部分的和第一部分的最后一个做比较,如果小就交换, alist = [26,54 93,17,77,31,44,55,20]