常见排序算法


一、归并排序

  归并是将两个或两个以上的有序表合并为一个新的有序表的过程。

1.归并排序

  在合并两个有序表的过程中,不断比较当前两个表中待操作元素,将其中较小(大)的放入新表,使得得到的新表也为有序。

二路归并排序:
  

void Merge(int left,int right,int mid){
	int i=left,j=mid+1;k=left;
	while(i<=mid && j<=right){
		if(SeqList.Key[i]<=SeqList.key[j]){
			MergedList.Key[k] = SeqList.Key[i];i++;k++;
		}
		else{
			MergedList.Key[k] = SeqList.Key[j];j++;k++;
		}
	}
	while(i<=mid){
		MergedList.Key[k] = SeqList.Key[i];
		j++;
		k++;		//插入当前剩余的元素
	}
	while(j<=right){
		MergedList.Key[k] = SeqList.Key[j];
		j++;
		k++;2
	}
	
}

复杂度分析

 设待归并的两个有序表长度分别为 m m m n n n,则二路归并后合成的表长度为 m + n m+n m+n;二路归并操作至多需要 m + n m+n m+n次比较和 m + n m+n m+n次移位,故二路归并的时间复杂度为 o ( m + n ) 。 o(m+n)。 o(m+n)

2. 2-路归并排序

  • 将n个记录看成是n个有序序列;
  • 将前后两两相邻的两个有序序列归并为一个有序序列(二路归并);
  • 重复二路归并操作,知道只有一个有序序列为止。

实现(伪代码):

List SeqList,MergedList;
void MergeSort(){
	int left,mid,right,step,n;
	n = SeqList.len;
	for(step=1;step<n;step*=2){
		for(left=1;left<=n;left+=2*step){	//选择前后两组,每组的长度为step,故每次递增左界left的值为2*step
		mid = left + step -1;
		if(mid>n) break;	//若此时越界则跳出循环
		right = left + 2*step -1;
		if(right>n) right=n;	//将整个数组的长度作为右界
		Merge(left,mid,rigt);	//调用归并排序算法
		}
		for(int i=1;i<=n;i++) SeqList.Key[i]=MergedList.Key[i];	 //更新序列的值
	}
}

复杂度分析

 若待排序记录为 n n n个,则需要做 l o g 2 n log_2n log2n趟二路归并排序,每趟二路归并排序的时间复杂度为 o ( n ) 。 o(n)。 o(n),因此2-路归并排序的时间复杂度为 o ( n l o g 2 n ) o(nlog_2n) o(nlog2n)。归并排序是一种稳定的排序方法。


二、基数排序

  有时候排序不只有一个关键字,可能包含多个;而基数排序是按待排序的关键字组成成分(或“位”)进行排序。

基数排序不需要进行关键字的比较和记录的移动,而是借助于多关键字排序思想实现单逻辑关键字的排序。

多关键字排序的基本思想:
  设有n个待排序记录,每个记录是由确定的 d d d(部分)位组成,每位有确定的 r e d i x redix redix种取值,则按照关键字的不同值将记录“分配”到 r a d i x radix radix各队列中后再“收集”,如此重复 d d d次,可以得到记录的有序序列,其间无需进行关键字间的比较

1.最高位优先(MSD,Most Significant Digit first)

即先排序最重要的关键字(位)。

  • 从最高位关键字 K 1 K^1 K1起进行排序,将记录序列分为若干子序列,每个子序列有相同的 K 1 K^1 K1值;

  • 分别对每个子序列再按 K 2 K^2 K2排序,每个子序列又被分为若干个更小的子序列;

  • 如此重复,直到将最后一个关键字 K d K^d Kd进行排序;

  • 最后,加个所有子序列依次联接成一个有序的序列。

2.最低位优先(LSD,Least Significant Digit first)

 思想与上述MSD一致,不过是从最低位关键字 K d K^d Kd排起。

LSD举例:
  

3.链式基数排序

以链表的结构实现基数排序,可以采取LSD或MSD。

  1. 首先以链表存储 n n n个待排序记录。

  2. 一趟排序的过程:

    分配:按 K i K^i Ki值的升序排序,改变记录指针,将链表中的记录结点依次分配到 r a d i x radix radix个链表队列中,每个队列中所有记录的对应关键字位 K i K^i Ki都相等,用f[i]e[i]作为第 i i i个链表队列的头指针和尾指针;

    收集:改变所有非空链队列的对位记录的指针域,使其指向下一个非空链队列的队头记录,从而将 r r r个链队列中的记录重新连接成一个链表;

  3. 如此依次再按关键字的重要级次序分别进行,共进行 d d d趟后即完成排序。


三、插入排序

1.直接插入排序

  • 不断将第 i i i个元素,插入到前面已排序的 r [ 1 ] , r [ 2 ] , … , r [ i − 1 ] r[1],r[2],…,r[i-1] r[1]r[2],r[i1]中;
  • r [ i ] r[i] r[i]的关键字与 r [ i − 1 ] , r [ i − 2 ] , … r[i-1],r[i-2],… r[i1]r[i2]的关键字顺序进行比较,若小于(大于) r [ x ] r[x] r[x],则将 r [ x ] r[x] r[x]及之后的元素向后移动(插入位置后的元素向后顺移);
  • r [ i ] r[i] r[i]插入。

代码实现:

#include<iostream>
 
using namespace std;
 
int main(){
    int num;
    cin>>num;
     
    int *array = new int[num];
    for(int i=0;i<num;i++) cin>>array[i];		//列表初始化
     
    for(int i=1;i<num;i++){
        int temp;
        for(int j=0;j<i;j++){			//由第0个元素开始,向后循环
            if(array[i]<=array[j]){
                temp = array[i];
                for(int k=i;k>j;k--){	//向后移动r[x]及之后的元素
                    array[k]=array[k-1];
                }
                array[j] = temp;		//插入r[i]
                break;
            }
        }
        for(int j=0;j<num-1;j++){
            cout<<array[j]<<" ";
        }
        cout<<array[num-1]<<endl;
    }
     
    delete []array;
}

2.希尔排序

 由直接插入排序可以看出,当待排序序列基本有序时,插入排序效率会提高;而希尔排序是先将待排序序列分为若干子序列,分别进行插入排序,待整个序列基本有序时,再对全体记录进行一次直接插入排序。于是,希尔排序又称为缩小增量排序
          

算法:

  • 取一个整数gap<n(待排序记录数)作为间隔,将全部记录分为gap个子序列,所有距离间隔为gap的记录为同一个子序列;
  • 在每个子序列列分别施行直接插入排序;
  • 缩小间隔gap
  • 重复上述子序列的划分与排序,直到最后gap=1,即对整个列表进行插入排序为止。


实现:

#include<iostream>
 
using namespace std;
 
class List{
private:
    int *list;
    int len;
public:
    List();
    ~List();
    void shellSort();
    void insertSort(int gap,int m);
};
 
List::List(){
    cin>>len;
    list = new int[len];
     
    for(int i=0;i<len;i++){
        cin>>list[i];
    }
}
 
List::~List(){
    delete []list;
}
 
void List::shellSort(){
    for(int gap=len/2;gap>=1;gap/=2){
        for(int m=0;m<gap;m++){			//内循环迭代不同的子序列
            insertSort(gap,m);
        }
         
        for(int j=0;j<len;j++){				//输出每一趟排序的结果
            cout<<list[j]<<" ";
        }
        cout<<endl;
    }
}
 
void List::insertSort(int gap,int m){		//插入排序
    for(int i=m;i<len;i+=gap){
        int temp;
        for(int j=m;j<i;j+=gap){
            if(list[j]<list[i]){
                temp = list[i];
                for(int k=i;k>j;k-=gap){
                    list[k] = list[k-gap];
                }
                 
                list[j] = temp;
                break;
            }
        }
    }
}

四、快速排序

 任取待排序记录中某个记录(例如第一个记录)为基准(枢),将记录序列划分为左右两个子序列。左侧子序列中的所有记录都小于或大于基准记录,右侧子序列中的所有记录都大于或等于基准记录的。然后递归地对两个子序列调用快速排序算法。

基准记录也称 枢纽(或支点) 记录。


算法:

  • 取序列第一个记录为枢纽记录,其关键字为Pivotkey

  • 指针low指向序列第一个记录位置,指针high指向序列最后一个记录位置;

  • 对序列进行一趟排序:

    1. high指向的记录开始,向前找到第一个小于Pivotkey的记录,将其放到low指向的位置,low++(由于Pivotkey保存了此时low指向的值,故不用担心数据覆盖);

    2. low指向的记录开始,向后找到第一个大于Pivotkey的记录,将其放到high指向的位置,high–(此时high的值已被换到上一步的low中了,故也不用担心数据被覆盖);

    3. 重复1,2,直到low=high,将枢纽记录放在low(high)指向的位置。

实现:

void qsort(int low,int high){
    int i=low,j=high;
    int pivot = list[low];
     
    while(low<high){
        while(low<high&&pivot<=list[high]) high--;
        if(low<high){
            list[low]=list[high];
            low++;
        }       
        while(low<high&&pivot>=list[low]) low++;
        if(low<high){
            list[high]=list[low];
            high--;
        }
    }
    list[low]=pivot;
     
    for(int i=0;i<len-1;i++){
        cout<<list[i]<<" ";
    }
    cout<<list[len-1]<<endl;
     
    if(i<low-1) qsort(i,low-1);
    if(j>high+1) qsort(high+1,j);
 
}

五、堆排序

堆在逻辑意义上为一棵二叉树

  • 定义:有一个关键字集合,按完全二叉树的顺序存储方式存放于一个一维数组中。若其中的每个节点的关键字都大于(或小于)其子节点的关键字,则称该关键字集合构成一个最大堆(最小堆)。

或称大顶堆/小顶堆。


 而使用堆排序主要要解决两个问题:

  1. 如何根据给定序列创建初始堆
  2. 如何在交换掉根节点后,将剩下的节点调整为新的堆(筛选)。

 首先看一次筛选:

1. 筛选

  • 比较根节点于两个子节点的值,若小于其中一个子节点,则选择最大的子节点与根节点交换
  • 继续将交换的节点与其子节点比较;
  • 直到叶子节点或根节点值大于两个子节点;

筛选操作保证了当前堆为最大堆,即根节点的值为整个堆的最小值。

2. 创建初始堆

  • 根据给定序列,从1至n按顺序创建完全二叉树;
  • 由最后一个非终端节点(==第n/2个节点)开始至第一个节点,逐步做筛选。

3. 堆排序

  • 创建初始堆,设置已排序序列与未排序序列;
  • 由于最大堆的根节点为整个未排序序列的最大值,故将根节点与未排序序列的最后一个节点交换,未排序序列-1,已排序序列+1;
  • 对交换完的堆进行从根节点开始的筛选;
  • 重复以上2~3。

若要实现升序排序,则创建大顶堆;若实现降序排序,则创建小顶堆。

堆排序举例:



实现:
由以上介绍可知,实现堆排序最重要的步骤即为定义筛选函数,该函数能递归地对当前节点及其子节点进行操作。

select(int n){
    int temp,min = list[n];
    int loc=n;
     
    if(((2*n+1)<ulen)&&(list[2*n+1]<min)){
        min = list[2*n+1];
        loc = 2*n+1;
    }
    if(((2*n+2)<ulen)&&(list[2*n+2]<min)){
        min = list[2*n+2];
        loc = 2*n+2;
    }
     
    temp = list[n];
    list[n]=min;
    list[loc]=temp;
     
     
    if(loc!=n){
    select(loc); 
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值