【基础算法】(01) 十二种排序算法(第一篇)

【基础算法】(01) 十二种排序算法(第一篇)

Auther: Thomas Shen
E-mail: Thomas.shen3904@qq.com
Date: 2017/10/20
All Copyrights reserved !


1. 总述:

本系列总结了常用的十二种排序算法,每个算法都包括算法原理, 代码实现, 面试例题 三部分。

其中本文是排序算法系列的第一篇,介绍了三种插入排序方法:

  • 2. 直接插入排序 (Straight Insertion Sort),
  • 3. 二分插入排序 (Binary Insert Sort),
  • 4. 希尔排序 (Shell’s Sort)

排序有内部排序和外部排序:

这里写图片描述

当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。

快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;

排序算法大体可分为两种:

  • 一种是比较排序,时间复杂度O(nlogn) ~ O(n^2),主要有:
    • 直接插入排序,希尔排序,
    • 冒泡排序,快速排序,
    • 简单选择排序,堆排序,
    • 归并排序,等。
  • 另一种是非比较排序,时间复杂度可以达到O(n),主要有:
    • 计数排序,基数排序,桶排序等。

这里写图片描述


2. 直接插入排序 (Straight Insertion Sort):

2.1 原理简介:
插入排序(Insertion Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

算法描述和分析:
一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

  1. 从第一个元素开始,该元素可以认为已经被排序;
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  5. 将新元素插入到该位置后;
  6. 重复步骤2~5。

这里写图片描述

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

如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数减去(n-1)次。

平均来说插入排序算法复杂度为O(n^2)。因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如,量级小于千,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)。

时间复杂度:O(n^2);

2.2 代码实现:

void print(int a[], int n ,int i){  
    cout<<i <<":";  
    for(int j= 0; j<8; j++){  
        cout<<a[j] <<" ";  
    }  
    cout<<endl;  
}  


void InsertSort(int a[], int n)  
{  
    for(int i= 1; i<n; i++){  
        if(a[i] < a[i-1]){ //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入  
            int j= i-1;   
            int x = a[i];  //复制为哨兵,即存储待排序元素  
            a[i] = a[i-1]; //先后移一个元素  
            while(x < a[j]){  //查找在有序表的插入位置  
                a[j+1] = a[j];  
                j--;         //元素后移  
            }  
            a[j+1] = x;      //插入到正确位置  
        }  
        print(a,n,i);        //打印每趟排序的结果  
    }  
}  

int main(){  
    int a[8] = {3,1,5,7,2,4,9,6};  
    InsertSort(a,8);  
    print(a,8,8);  
}  

2.3 面试例题:

2.3.1 链表的插入排序程序:

template<typename T>  
struct list_node  
{  
    struct list_node<T> *next;  
    T value;  
};  
template<typename T>  
struct _list  
{  
    struct list_node<T> *head;  
    int size;  
};  
template<typename T>  
void SortLink(struct _list<T> * link) {  
    struct list_node<T> *pHead,*pRear,*p,*tp;  
    if (!link) return;  
    for (pHead=link->head,pRear=0;pHead;pHead=pHead->next) {  
        for (tp=pHead,p=pHead->next;p;tp=p,p=p->next)  
            if (pHead->value>=p->value)  
                tp->next=p->next,p->next=pHead,pHead=p,p=tp;  
        if (!pRear) link->head=pHead;  
        else pRear->next=pHead;  
        pRear=pHead;  
    }  
}  

3. 二分插入排序 (Binary insert sort):

二分(折半)插入(Binary insert sort)排序是一种在直接插入排序算法上进行小改动的排序算法。其与直接排序算法最大的区别在于查找插入位置时使用的是二分查找的方式,在速度上有一定提升。

3.1 原理简介:

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

  1. 从第一个元素开始,该元素可以认为已经被排序;
  2. 取出下一个元素,在已经排序的元素序列中二分查找到第一个比它大的数的位置;
  3. 将新元素插入到该位置后;
  4. 重复上述两步。
  • 稳定
  • 空间代价:O(1)
  • 时间代价:插入每个记录需要O(log i)比较,最多移动i+1次,最少2次。最佳情况O(n log n),最差和平均情况O(n^2)。

二分插入排序是一种稳定的排序。当n较大时,总排序码比较次数比直接插入排序的最差情况好得多,但比最好情况要差,所元素初始序列已经按排序码接近有序时,直接插入排序比二分插入排序比较次数少。二分插入排序元素移动次数与直接插入排序相同,依赖于元素初始序列。

3.2 代码实现:

void BinInsertSort(int a[], int n)   
{   
    int key, left, right, middle;   
    for (int i=1; i<n; i++)   
    {   
        key = a[i];
        left = 0;   
        right = i-1;   
        while (left<=right)
        {   
            middle = (left+right)/2;   
            if (a[middle]>key)   
                right = middle-1;   
            else
                left = middle+1;
        }
        for(int j=i-1; j>=left; j--)   
        {   
            a[j+1] = a[j];
        }              
        a[left] = key;          
    }   
}  

4. 希尔(Shell)排序:

希尔排序(Shell`s Sort)相对直接排序有较大的改进。希尔排序又叫缩小增量排序。

4.1 原理简介:

  1. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  2. 按增量序列个数k,对序列进行k 趟排序;
  3. 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

希尔排序

4.2 代码实现:

我们简单处理增量序列:增量序列d = {n/2 ,n/4, n/8 …..1} n为要排序数的个数

即:先将要排序的一组记录按某个增量d(n/2,n为要排序数的个数)分成若干组子序列,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。继续不断缩小增量直至为1,最后使用直接插入排序完成排序。
void print(int a[], int n ,int i){  
    cout<<i <<":";  
    for(int j= 0; j<8; j++){  
        cout<<a[j] <<" ";  
    }  
    cout<<endl;  
}  
/** 
 * 直接插入排序的一般形式 
 * @param int dk 缩小增量,如果是直接插入排序,dk=1 
 */  

void ShellInsertSort(int a[], int n, int dk)  
{  
    for(int i= dk; i<n; ++i){  
        if(a[i] < a[i-dk]){          //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入  
            int j = i-dk;     
            int x = a[i];           //复制为哨兵,即存储待排序元素  
            a[i] = a[i-dk];         //首先后移一个元素  
            while(x < a[j]){     //查找在有序表的插入位置  
                a[j+dk] = a[j];  
                j -= dk;             //元素后移  
            }  
            a[j+dk] = x;            //插入到正确位置  
        }  
        print(a, n,i );  
    }  

}  

/** 
 * 先按增量d(n/2,n为要排序数的个数进行希尔排序 
 */  
void shellSort(int a[], int n){  
    int dk = n/2;  
    while( dk >= 1  ){  
        ShellInsertSort(a, n, dk);  
        dk = dk/2;  
    }  
}  

int main(){  
    int a[8] = {3,1,5,7,2,4,9,6};  
    //ShellInsertSort(a,8,1); //直接插入排序  
    shellSort(a,8);           //希尔插入排序  
    print(a,8,8);  
}  

希尔排序时效分析很难,关键码的比较次数与记录移动次数依赖于增量因子序列d的选取,特定情况下可以准确估算出关键码的比较次数和记录的移动次数。目前还没有人给出选取最好的增量因子序列的方法。增量因子序列可以有各种取法,有取奇数的,也有取质数的。

但需要注意:增量因子中除1 外没有公因子,且最后一个增量因子必须为1。

希尔排序方法是一个不稳定的排序方法。


5. 选择排序—简单选择排序(Simple Selection Sort):
6. 选择排序—堆排序(Heap Sort):
7. 交换排序—冒泡排序(Bubble Sort):
8. 鸡尾酒排序/双向冒泡排序:
9. 交换排序—快速排序(Quick Sort):
10. 归并排序(Merge Sort):
11. 桶排序 (Bucket sort):
12. 计数排序 (Counting sort):
13. 基数排序 (Radix Sort):

参见第二、三、四篇;


14. 总结:

这里写图片描述


References. :
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值