常见的排序算法总结

整理常用的排序算法,主要包括:

  • 选择排序
  • 冒泡排序
  • 归并排序
  • 快速排序
  • 插入排序
  • 堆排序

策略以及时间开销

排序算法 基于的思想 时间开销
选择排序 蛮力法 O(n^2)
冒泡排序 蛮力法 O(n^2)
归并排序 分治法 O(nlogn)
快速排序 分治法 O(nlogn)
插入排序 减治法 O(n^2)
堆排序 减治法 O(nlogn)

部分排序的核心算法

归并排序

图解

代码

/*
输入:待排序的数组r[n],待排序区间[s,t]
输出:升序序列r[s]~r[t]
1. 如果s==t,则待排序区间只有一个元素,返回
2. 计算划分重点:m=(s+t)/2
3. 对于前半个子序列以 r[s]~r[m]进行递归,继续划分
4. 对于后半个子序列以 r[m+1]~r[t]进行递归,继续划分
5. 合并划分后的子序列:
	5.1 令i=s,j=m+1,k=s 在 i<=m&&j<=t时进行循环
		5.1.1 按照升序依次在两个子序列选择元素放入r1[]临时数组,同时改变两个子序列的索引位置
6. 处理某个子序列未放完的元素
7. 将r1复制到r中
8. 待所有递归过程结束,打印r[]
*/

void MergeSort(int r[],int s,int t){
    int m,r1[1000];
    
    if(s==t) return;		// 此时划分至只有一个元素了
	
    else{
        m = (s+t)/2;		// 继续划分
        MergeSort(r,s,m);	// 划分前半个子序列
        MergeSort(r,m+1,t);	// 划分后半个子序列
        Merge(r,r1,s,m,t);	// 合并划分后的结果
        
        for(int i=s;i<=t;i++){
            r[i]=r1[i];
        }   
    }
}

/* 合并函数 */
void Merge(int r[],int r1[],int s,int m,int t){
    
    int i=s;
    int j=m+1;		// 分别标记原数组两个子序列的起始位置
    int k=s;		// 标记r1临时数组
    
    while(i<=m&&j<=t){
        if(r[i]<=r[j])	r1[k++]=r[i++];
        else	r1[k++]=r[j++];
    }
    
    // 进行收尾工作
    while(i<=m) r1[k++]=r[i++];
    while(j<=t) r1[k++]=r[j++];
    
}

快速排序

图解

代码

/*
伪代码:
(假设以首元素为轴值)
输入:r[n],first,end
输出:升序排序的r[n]
1. 选定首元素即 r[first] 为轴值
2. 如果first<end
	2.1 划分左右区间,获得轴值位置
		2.1.1 令 i=first j=end, if i<j 做:
		2.1.2 右侧扫描,if r[j]>轴值 j-- ;反之 swap(r[i],r[j])
		2.1.3 左侧扫描,if r[i]<轴值 i++ ;反之 swap(r[i],r[j])
	2.2 返回最后的i即为轴值的最新位置,pivot<-i
	2.3 左孩子序列以:r,first,pivot-1 快排
    2.4 右孩子序列以:r,pivot+1,end 快排
3. 最后输出排完序的r[n]
*/

void QuickSort(int r[],int first,int end){
    int pivot;		// 轴值
    
    if(first<end){
    	pivot=Partition(r,first,end);	// 划分子序列,pivot是轴值所在位置的索引
        QuickSort(r,first,pivot-1);		// 左子序列快排
        QuickSort(r,pivot+1,end);		// 右子序列快排
    }
}

/* 划分子序列,求解轴值位置 */
int Partition(int r[],int first,int end){
    int i=first;
    int j=end;
    
    while(i<j){
        while(i<j&&r[i]<=r[j])	j--;
        
        if(i<j){
        	int temp=r[i];
            r[i]=r[j];
            r[j]=temp;
            i++;
        }
        
        while(i<j&&r[i]<=r[j])	i++;
        
        if(i<j){
            int temp=r[i];
        	r[i]=r[j];
            r[j]=temp;
            j--;
        }
    }
    return i;
}

插入排序

图解

代码

// 设r[0]为哨兵,实际的数据从r[1]开始
for(int i=2;i<=n;i++){
    r[0]=r[i];		// 使得r[0]一直为无序序列的第一个一个元素
    for(int j=i-1;r[0]<r[j];j--){
        r[j+1]=r[j];		// 如果待插入元素比有序序列中某个元素小,这个元素后移
    }
    r[j+1]=r[0];
}

堆排序

图解

代码

/*
伪代码:
输入:r(k+1)~r(n)满足堆的条件,待筛选记录rk
输出:{r(k)',r(k+1)',...r(n)'}为大根堆
1. 设置i和j,分别指向要筛选的结点和其左孩子结点
2. 若ri已是叶子,则筛选完毕
	否则,比较要筛选结点的左右孩子结点,并将j指向较大的结点
3. 将ri和rj进行比较,有以下两种情况:
	3.1 如果ri>rj,则完全二叉树已是堆,筛选完毕
	3.2 否则将ri和rj进行交换;令i=j,转步骤2继续执行
*/

void HeapSort(int r[],int n){
    int i,temp;
    
    for(i=(n-1)/2;i>=0;i--){		// 初始建堆,最后一个分支下标为(n-1)/2
        SiftHeap(r,i,n);
    }
    
    for(i=1;i<=n-1;i++){
        temp=r[0];
        r[0]=r[n-i];
        r[n-i]=temp;
        SiftHeap(r,0,n-i);			// 只需要调整根节点
    }
}

void SiftHeap(int r[],int k,int n){
    int i,j,temp;					// 置i为要筛的结点,j为i的左孩子
    i=k;
    j=i*2+1;
    while(j<n){
        if(j<n-1&&r[j]<r[j+1]) j++;		// 比较i的左右孩子,j为较大者
        if(r[i]>r[j]) 	break;
        else{
            // 将被筛结点与结点j交换
            temp=r[i];
            r[i]=r[j];
            r[j]=temp;
            // 被筛节点位于原来结点j的位置
            i=j;
            j=2*i+1;
        }
    }
}
Licensed under CC BY-NC-SA 4.0
自认为是幻象波普星的来客
Built with Hugo
主题 StackJimmy 设计