排序算法全攻略 1

虽然排序算法是一个简单的问题,但绝对是笔试面试的基础考点,重重之重。来个排序问题都没回答出来,留给面试官的印象也就那样了。

排序主要分为:

比较排序:快速排序、堆排序、归并排序、插入排序、希尔排序、选择排序、冒泡排序

非比较排序:基数排序、计数排序、桶排序

性能比较点:

时间复杂度:一般而言,好的性能是O(nlgn),且坏的性能是O(n^2)。对于一个排序理想的性能是O(n)

稳定性:是否能让原本有相等键值的纪录维持相对次序。

 

一、插入排序

《算法导论》的第2章介绍了插入排序及其算法分析。

核心:有序序列+直接插入

描述:维持一个有序区,将无序区的第一个元素直接插入到有序区,形成新的有序序列,最终实现排序。最优、平均、最差时间复杂度为θ(n^2)。

算法步骤为:

1、        从第一个元素开始,该元素可以认为已经被排序

2、        取出下一个元素,在已经排序的元素序列中从后向前扫描

3、        如果该元素(已排序)大于新元素,将该元素移到下一位置

4、        重复步骤3,直到找到已排序的元素小于或者等于新元素的位置

5、        将新元素插入到该位置后

6、        重复步骤2~5

上图:


伪代码为:

INSERTION-SORT(A)
for j <- 2 to length[A]
        do key <- A[j] 
               Insert A[j] into the sorted sequence A[1..j-1] 
               i  <- j-1 
        while i>0 and A[i]>key
               do A[i+1] <- A[i] 
                       i  <- i-1 
        A[i+1] <- key


实现:

#include<assert.h>
#include<iostream>
#include<algorithm>
#include<iterator>
usingnamespace std;
voidinsert_sort(int *a,int len){
    assert(a!=NULL && len>0);
    int key=0,i=0;
    for(int pos=1;pos<len;++pos){
       key = a[pos];
       i = pos-1;
       while(i>=0 && a[i]>key){//backward
           a[i+1]=a[i];
           i--;
       }
       a[i+1] = key;
    }
}
int main(){
    int seq[]={3,7,8,5,2,1,9,5,4};
    int length=sizeof(seq)/sizeof(int);
    copy(seq,seq+length,ostream_iterator<int>(cout,""));
    cout<<endl;
    insert_sort(seq,length);
    copy(seq,seq+length,ostream_iterator<int>(cout,""));
    cout<<endl;
    return 0;
}


结果:


Eg. 请写出链表的插入排序程序 (copy过来的)

template<typenameT>
structlist_node{
    struct list_node<T> *next;
    T value;
};
template<typenameT>
struct _list{
    struct list_node<T> *head;
    int size;
};
template<typenameT>
voidSortLink(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;
    }
}



二、二分查找排序

二分查找排序是插入排序的一个变种。改进点:对有序区从末尾一个一个直接比较,改为效率更高的二分查找。在速率上有一定的提升。二分插入排序元素移动次数与直接插入排序相同,最佳情况O(nlgn),最差和平均情况O(n^2)

实现:

voidbinary_insert_sort(int *a,int len){
    assert(a!=NULL && len>0);
    int begin=0,end=0,middle=0;
    int key=0,i=0;
    for(int pos=1;pos<len;++pos){
       key = a[pos];
       begin=0;
       end=pos-1;
       while(begin<=end){
           middle = (begin+end)/2;
           if(a[middle]>key)
              end=middle-1;
           else
              begin=middle+1;
       }
 
       i=pos-1;
       while(i>=begin){
           a[i+1]=a[i];
           --i;
       }
       a[begin] = key;
    }
}


 

三、希尔排序

Shell sort,递减增量排序算法,因DL.Shell于1959年提出而得名,是插入排序的一种更高效的改进版本。

核心:增量分组+插入排序+增量递减

描述:希尔排序是非稳定排序算法,希尔排序的时间复杂度与增量序列的选取有关,希尔增量时间复杂度为O(n^2)。

步骤:

1、先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中,在各组内进行直接插入排序。

2、取第二个增量d2<d1重复上述的分组和排序,

3、直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。

上图:


伪代码

input: an array a of length n with array elements numbered 0 to n − 1
inc ← round(n/2)
while inc > 0 do:   
    for i = inc .. n − 1 do:       
        temp ← a[i]       
        j ← i       
        while j ≥ inc and a[j − inc]> temp do:           
            a[j] ← a[j − inc]           
            j ← j − inc        
        a[j] ← temp   
    inc ← round(inc / 2)


实现:

#include <assert.h>
#include <iostream>
#include <algorithm>
#include <iterator>
using namespace std;
void shell_sort(int *a,int len){
	assert(a!=NULL && len>0);
	int key=0;
	for(int gap=len/2;gap>0;gap/=2){
		for(int i=gap;i<len;++i){
			key=a[i];	
			int j=i-gap;		
			while(j>=0 && a[j]>key){
				a[j+gap]=a[j];
				j-=gap;
			}
			a[j+gap]=key;
		}
}
int main(){
	int seq[]={3,7,8,5,2,1,9,5,4};
	int length=sizeof(seq)/sizeof(int);	
	copy(seq,seq+length,ostream_iterator<int>(cout," ")); 
	cout<<endl;
	cout<<"begin: "<<endl;
	shell_sort(seq,length);
	cout<<"end: "<<endl;
	copy(seq,seq+length,ostream_iterator<int>(cout," ")); 
	cout<<endl;
	return 0;
}

结果


四、选择排序

Selection sort是一种简单直观的排序算法

核心:有序区+选无序区的极值

描述:将无序区的最值放在有序区的末尾,以此对序列进行排序最好、平均和最坏运行时间为θ(n^2)。

算法步骤为:(此处为递减序列,递增则选无序区的最大值)

1、初始状态:无序区为R[1..n],有序区为空。

2、第i趟排序开始时,当前有序区和无序区分别为R[1..i-1]和R[i…n]。该趟排序从当前无序区中选出关键字最小的记录R[k],将它与无序区的第1个记录R交换,使R[1..i]和R分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。

3、前n-1趟结束,数组有序化了

上图:


伪代码为:

SELECTION-SORT(A)
for j = 1 to Length(A)
        i = j
        key = A(i)
        for i to Lenth(A)
               if key>A(i)
                       key = A(i)
                       k = i
        A(k) = A(j)
        A(j)  = key


实现:

#include <assert.h>
#include <iostream>
#include <algorithm>
#include <iterator>
using namespace std;
void select_sort(int *a,int len){
	assert(a!=NULL && len>0);
	int max=0,pos=0;
	for(int i=0;i<len;++i){
		max=a[i];
		pos=i;
		for(int j=i;j<len;++j){
			if(a[j]>max){
				pos=j;
				max=a[j];
			}
		}
		swap(a[i],a[pos]);
	}
}
int main(){
	int seq[]={3,7,8,5,2,1,9,5,4};
	int length=sizeof(seq)/sizeof(int);	
	copy(seq,seq+length,ostream_iterator<int>(cout," ")); 
	cout<<endl;
	cout<<"begin: "<<endl;
	select_sort(seq,length);
	cout<<"end: "<<endl;
	copy(seq,seq+length,ostream_iterator<int>(cout," ")); 
	cout<<endl;
	return 0;
}

结果:



五、归并排序

《算法导论》的第2章介绍了归并排序及其算法分析,并引入了分治算法策略,divide-and-conquer。

核心:分治

描述:指的是将两个已经排序的串行合并成一个串行的操作。最坏情况下运行时间为θ(n^2),但是平均性能相当好,期望的运行时间为θ(nlgn)。

算法步骤为:

1、        Divide: 把长度为n的输入序列分成两个长度为n/2的子序列

2、        Conquer: 对这两个子序列分别采用归并排序

3、        Combine: 将两个排序好的子序列合并成一个最终的排序序列。

上图:

 

伪代码为:

MERGE(A,p,q,r)
N1←q-p+1
N2←r-q
Creat arrays L[1……n1+1] and R[1….n2+1]
For i←1 to n1
Do l[i]←A[p+i-1]
For j←1 to n2
        Do R[j]←A[q+j]
L[n1+1]←∞
R[n2+1]←∞
i←1
j←1
for k←p to r
        do if Li]<=R[j]
               then A[k]←l[j]
               i←i+1   
        else A[k]←R[j]
               j←j+1

NERGE_SORT(A,p,r)
If p<r
Then q←[(p+r)/2]
        MERGE_SORT(A,p,q)
MERGE_SORT(A,p+1,q)
MERGE_SORT(A,p,q,r)


实现

#include<assert.h>
#include<iostream>
#include<algorithm>
#include<iterator>
usingnamespace std;
//combinea[begin,middle] with a[middle+1,end]
voidcombine_array(int *a,int b,int m,int e,int *temp){
    assert(a!=NULL && b>=0 &&m>=0 && e>=0 && temp!=NULL);
    int i=b,j=m+1,pos=0;
    while(i<=m && j<=e){
       if(a[i]<=a[j])
           temp[pos++]=a[i++];
       else
           temp[pos++]=a[j++];
    }
    while(i<=m)
       temp[pos++]=a[i++];
    while(j<=m)
       temp[pos++]=a[j++];
    for(i=0;i<pos;++i){
       a[b+i]=temp[i];
    }
}
voidmerge_sort(int *a,int begin,int end,int *temp){
    assert(a!=NULL && begin>=0&& end>=0 && temp!=NULL);
    if(begin<end){
       int middle = (begin+end)/2;
       merge_sort(a,begin,middle,temp);//left
       merge_sort(a,middle+1,end,temp);//rigth
       combine_array(a,begin,middle,end,temp);//combine
    }
}
int main(){
    int seq[]={3,7,8,5,2,1,9,5};
    int length=sizeof(seq)/sizeof(int);
    int *t = new int(length);
    copy(seq,seq+length,ostream_iterator<int>(cout,""));
    cout<<endl;
    cout<<"begin: "<<endl;
    merge_sort(seq,0,length-1,t);
    cout<<"end: "<<endl;
    copy(seq,seq+length,ostream_iterator<int>(cout,""));
    cout<<endl;
    delete t;
    return 0;
}


结果:

 

六、冒泡排序

Bubble sort是一种简单的排序算法。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

核心:比大小

描述:冒泡排序是与插入排序拥有相等的执行时间。最优O(n),平均、最初O(n^2)。

步骤

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

2、对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

3、针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

上图


伪代码

function bubblesort (A : list[0..n-1]) {
    var inti, j;
    for i fromn-1 downto 0 {
        for j from 0 to i {
            if(A[j] > A[j+1])
               swap(A[j], A[j+1])
        }
    }
}


实现

#include<assert.h>
#include<iostream>
#include<algorithm>
#include<iterator>
usingnamespace std;
voidbubble_sort(int *a,int len){
    assert(a!=NULL && len>0);
    for(int i=0;i<=len-1;++i){
       for(int j=0;j<=len-1-i;++j)
           if(a[j]>a[j+1])
              swap(a[j],a[j+1]);
    }
}
int main(){
    int seq[]={3,7,8,5,2,1,9,5,4};
    int length=sizeof(seq)/sizeof(int);   
    copy(seq,seq+length,ostream_iterator<int>(cout,""));
    cout<<endl;
    cout<<"begin: "<<endl;
    bubble_sort(seq,length);
    cout<<"end: "<<endl;
    copy(seq,seq+length,ostream_iterator<int>(cout,""));
    cout<<endl;
    return 0;
}

结果

 

七、快速排序

算法导论的第七章介绍了快速排序及其算法分析。

核心:分治+递归

描述:快速排序采用的是分治算法思想,分而治之,各个击破。最坏情况下运行时间为θ(n^2),但是平均性能相当好,期望的运行时间为θ(nlgn)。

算法步骤为:

1、pivot:从数列中挑出一个元素作为基准

2、partition:重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。

3、recursive:把两个子序列递归排序

上图:


伪代码为:

function quicksort(q)
     var list less,pivotList, greater
     if length(q) ≤ 1 {
         return q
     } else {
         select a pivot value pivotfrom q
         for each x inq except the pivot element
             if x < pivot thenadd x to less
             if x ≥ pivot thenadd x to greater
         add pivot topivotList
         returnconcatenate(quicksort(less), pivotList, quicksort(greater))
     }


实现:快排的基准值可以以多种方式获得。取首元素,末尾元素,或者干脆来个随机取值。

维基的百科的实现非常经典,代码如下

struct Range{
        explicit Range(int s=0,int e=0):start(s),end(e){}
        int start,end;
};
void quicksort(int n,int arr[]){
        if(n<=0) return;
        stack<Range> st;
        st.push(Range(0,n-1));
        while(!st.empty()){
               Range range = st.top();
               st.pop();
               int pivot = arr[range.end];
               int pos = range.start-1;
               for(int i=range.start;i<range.end;++i){
                       if(arr[i]<pivot){
                               std::swap(arr[i],arr[++pos]);
                       }
               }
               std::swap(arr[++pos],arr[range.end]);
               if(pos-1>range.start){
                       st.push(Range(range.start,pos-1));
               }
               if(pos+1<range.end){
                       st.push(Range(pos+1,range.end));
               }
        }
}


自己的实现代码,加了个判断,相等就不交换:

#include<assert.h>
#include<iostream>
#include<algorithm>
#include<iterator>
usingnamespace std;
voidquick_sort(int *a,int len){
    assert(a);
    int pivot=0,low=0,pos=0;
    if(len>1){
       pivot = a[len-1];
       for(pos=0,low=0;pos<len-1;++pos){
           if(a[pos]<pivot){
              if(a[pos]==a[low]){
                  ++low;
                  continue;
              }
              swap(a[pos],a[low++]);
           }
       }
       swap(a[low],a[len-1]);
       quick_sort(a,low);
       quick_sort(a+low+1,len-low-1);
    }
}
 
int main(){
    int seq[]={3,7,8,5,2,1,9,5,4};
    int length=sizeof(seq)/sizeof(int);
    copy(seq,seq+length,ostream_iterator<int>(cout,""));
    cout<<endl;
    quick_sort(seq,length);
    copy(seq,seq+length,ostream_iterator<int>(cout,""));
    cout<<endl;
    return 0;
}

运行结果:

 

八、堆排序

《算法导论》的第6章引入了堆、最大堆、最小堆的概念,由此引入了堆排序。Heap sort是利用数据结构堆所设计的一种排序算法。堆的性质是即子结点的键值或索引总是小于(或者大于)它的父节点。

核心:最大(小)堆+建立堆+堆调整

 

堆节点的性质:父节点i的左子节点在位置 (2*i+1);父节点i的右子节点在位置 (2*i+2);子节点i的父节点在位置 floor((i-1)/2);

描述,最优、平均和最差时间复杂度O(nlgn)

 

最大堆调整(Max_Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点


从图中可以看出,在节点i=2时,不满足最大堆的要求,需要进行调整,选择节点2的左右孩子中最大一个进行交换,然后检查交换后的节点i=4是否满足最大堆的要求,从图看出不满足,接着进行调整,直到没有交换为止。

递归形式:

voidadjust_max_heap_recursive(int *datas,int length,int i){
    int left,right,largest;
    int temp;
    left = LEFT(i);   //left child
    right = RIGHT(i); //right child
    //find the largest value among left andrihgt and i.
    if(left<=length && datas[left]> datas[i])
        largest = left;
    else
        largest = i;
    if(right <= length &&datas[right] > datas[largest])
        largest = right;
    //exchange i and largest
    if(largest != i){
        temp = datas[i];
        datas[i] = datas[largest];
        datas[largest] = temp;
        //recursive call the function,adjustfrom largest
        adjust_max_heap(datas,length,largest);
    }
}


非递归形式:

voidadjust_max_heap(int *datas,int length,int i){
    int left,right,largest;
    int temp;
    while(1){
        left = LEFT(i);   //left child
        right = RIGHT(i); //right child
        //find the largest value among left andrihgt and i.
        if(left <= length &&datas[left] > datas[i])
            largest = left;
        else
            largest = i;
        if(right <= length &&datas[right] > datas[largest])
            largest = right;
        //exchange i and largest
        if(largest != i){
            temp = datas[i];
            datas[i] = datas[largest];
            datas[largest] = temp;
            i = largest;
            continue;
        }
        else
            break;
    }
}


创建最大堆(Build_Max_Heap):将堆所有数据重新排序,从最后一个非叶子节点(n/2)开始调整。

 


voidbuild_max_heap(int *datas,int length)
{
    int i;
    //build max heap from the last parent node
    for(i=length/2;i>0;i--)
        adjust_max_heap(datas,length,i);
}


堆排序(HeapSort):第一个数据的根节点与最后一个节点交换,堆长度减1,并做最大堆调整的递归运算

(1)创建最大堆,数组第一个元素最大,执行后结果下图:


(2)进行循环,从length(a)到2,并不断的调整最大堆,给出一个简单过程如下:


排序函数:

voidheap_sort(int *datas,int length){
    int i,temp;
    //bulid max heap
    build_max_heap(datas,length);
    i=length;
    //exchange the first value to the lastunitl i=1
    while(i>1){
        temp = datas[i];
        datas[i] = datas[1];
        datas[1] =temp;
        i--;
        //adjust max heap,make sure the fisrtvalue is the largest
        adjust_max_heap(datas,i,1);
    }
}


结果

#include<iostream>
usingnamespace std;
void sift(intd[], int ind, int len){
    //#置i为要筛选的节点#%
    int i = ind;
    //#c中保存i节点的左孩子#%
    int c = i * 2 + 1; //#+1的目的就是为了解决节点从0开始而他的左孩子一直为0的问题#%
    while(c < len)//#未筛选到叶子节点#%{
        //#如果要筛选的节点既有左孩子又有右孩子并且左孩子值小于右孩子#%
        //#从二者中选出较大的并记录#%
        if(c + 1 < len && d[c] <d[c + 1])
            c++;
        //#如果要筛选的节点中的值大于左右孩子的较大者则退出#%
        if(d[i] > d[c]) break;
        else{
            //#交换#%
            int t = d[c];
            d[c] = d[i];
            d[i] = t;
            //
            //#重置要筛选的节点和要筛选的左孩子#%
            i = c;
            c = 2 * i + 1;
        }
    }
    return;
}
 
voidheap_sort(int d[], int n){
    //#初始化建堆, i从最后一个非叶子节点开始#%
    for(int i = (n - 2) / 2; i >= 0; i--)
        sift(d, i, n);
    for(int j = 0; j < n; j++){
                //#交换#%
        int t = d[0];
        d[0] = d[n - j - 1];
        d[n - j - 1] = t;
        //#筛选编号为0 #%
        sift(d, 0, n - j - 1);
    }
}
int main(){
    int a[] = {4,1,3,16,9,10,14,8,7};
    heap_sort(a, sizeof(a) / sizeof(*a));
    for(int i = 0; i < sizeof(a) /sizeof(*a); i++){
        cout << a[i] << ' ';
    }
    cout << endl;
    return 0;
}


只有消化了的思想和实现才是肚子里的料。今天涂鸦移动电面挂了,慢慢走吧,路还长着呢。。。

堆排序这里,写得不咋的,以后再修改。

 

参考:

http://zh.wikipedia.org/wiki/%E6%8E%92%E5%BA%8F

http://www.cnblogs.com/Anker/category/436374.html

http://www.cnblogs.com/Anker/archive/2013/01/23/2873422.html

http://blog.csdn.net/pi9nc/article/details/12220851


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值