《算法修行_第一章:碧血剑之排序十式》一篇介绍十种常见排序算法的文章

碧血剑十式:《碧血剑》是金庸先生创作的武侠小说之一,主角血刀老祖所掌握的剑法,共有十种不同的剑式,每一式都有各自独特的特点。

如果将学习算法的过程比喻成武侠小说,排序算法可以看作是基础功夫的阶段。

排序排名(简入难)

序号排序算法空间复杂度时间复杂度
1冒泡排序(Bubble Sort)O(1)O(n^2)
2选择排序(Selection Sort)O(1)O(n^2)
3插入排序(Insertion Sort)O(1)O(n^2)
4希尔排序(Shell Sort)O(1)平均:O(n log n)
5归并排序(Merge Sort)O(n)O(n log n)
6快速排序(Quick Sort)平均:O(log n)最差:O(n^2)
7堆排序(Heap Sort)O(1)O(n log n)
8计数排序(Counting Sort)O(k)O(n + k)
9桶排序(Bucket Sort)O(n + k)O(n^2)
10基数排序(Radix Sort)O(n + k)O(n * k)

大家可以根据难易程度观看,我会根据反馈进行推细讲难点!!!!!

1. 一剑化三清 - 快速排序(Quick Sort):快速排序以一次划分为基准,将数据分成两部分,类似一剑斩断三人

原理
通过选择一个基准元素,将序列划分为两个子序列,其中一个子序列的元素都小于等于基准元素,另一个子序列的元素都大于等于基准元素,然后对两个子序列递归地进行快速排序。(难点)

总结:选择基准,划分数组,递归排序,合并子数组,最终得到排序后的数组。

注重先找右边最小值!!!

ezgif-4-aa244bfbe7.gif

#include <iostream>

using namespace std;

void constantSpaceComplexity(int n[], int begin, int end) {
    if (begin >= end) {
        return;
    }
    int pivot = n[begin];
    int i = begin;
    int j = end;
    while (i != j) {
    	 while (n[j] >= pivot && j > i) {
            j--;
        }
        while (n[i] <= pivot && j > i) {
            i++;
        }
       
        if (i < j) {
            swap(n[i], n[j]);
        }
    }
    swap(n[begin], n[j]);
    //第一次调整的值7 4 9 49 17 21 24 30 48 23 43
    constantSpaceComplexity(n, begin, j - 1);
    constantSpaceComplexity(n, j + 1, end);
}
int main() {
    int n[11] = {9, 30, 7, 49, 17, 21, 24, 4, 48, 23, 43};
    constantSpaceComplexity(n,0,10);
//    cout << "排序后的数组:";
    for (int i = 0; i < 11; i++) {
        cout << n[i] << " ";
    }
    cout << endl;
    return 0;	
}

第一次调整时间函数代码分析
语句N(0)=9基准数第一次调整的值9 30 7 49 17 21 24 4 48 23 43目的
while (n[j] >= pivot && j > i) {j–;}9, 30, 7, 49, 17, 21, 24, 4, 48, 23, 43从右边往左找到比9小的数
while (n[i] <= pivot && j > i) {i++;}9, 30, 7, 49, 17, 21, 24, 4, 48, 23, 43从左边往右找到比9大的数
if (i < j) {swap(n[i], n[j]);}判断j是否小于防止当前数组越过本次循环的值n[i]与n[j]交换数值
swap(n[begin], n[j])这里n[i]或n[j],因为当两值交互数字肯定比基准数小这里交换9与7值
constantSpaceComplexity(n,begin,j-1);处理7 4 9这使用递归处理前端数字
constantSpaceComplexity(n, j + 1, end)处理17 21 24 30 48 23 43这使用递归处理后端数字

2. 二仙争长短 - 归并排序(Merge Sort):归并排序将两个有序的子数组合并成一个有序数组,如同两仙较量长短。

原理
将序列划分为较小的子序列,递归地对子序列进行排序,然后将排好序的子序列合并成一个更大的有序序列。

ezgif-1-ffab8a0a44.gif

简单解析:

  1. 第一次9, 30, 7, 49, 17, 21,分为两组,两组9,30与7,49都符合从小到大
  2. 9,30,7与49, 17, 21,代码排序
#include <iostream>

using namespace std;

void constantSpaceComplexity(int n[], int begin, int end) {
	if(begin>=end){
		return;
	}
        //进行分组
	int len=begin+(end-begin)/2;
	constantSpaceComplexity(n,begin,len);
	constantSpaceComplexity(n,len+1,end);
	int len1=len-begin+1;//获取左边长度
	int len2=end-len;//获取右边长度
	int* Array1 = new int[len1];//申请左长度的临时空间
	int* Array2 = new int[len2];//申请右长度的临时空间
	for(int i=0;i<len1;i++){Array1
		Array1[i]=n[begin+i];//将n[0+0],将n[0+1]赋值给临时空间Array1:7,9,30, 
	}
	for(int j=0;j<len2;j++){
		Array2[j]=n[len+1+j];//将n[2+0],将n[2+1]赋值给临时空间Array2: 17, 21 ,49
	}
	int i=0;int j=0;
	int k=begin;
        //len1=3||len2=3
	while(i<len1 && j<len2){
                //判断Array1[0]<Array2[0] 7<17,9<17,30<17,30<21
		if(Array1[i]<Array2[j]){
                        //7<17,9<17导致i++,k++  n[0]=7,n[1]=9,n[4]=30
			n[k++]=Array1[i++];
		}else{
                        //30<17导致j++,k++ n[2]=17,n[3]=21,n[5]=49
			n[k++]=Array2[j++];
		}
	}
	
    while (i < len1) {
        n[k++] = Array1[i++];
    }

    while (j < len2) {
        n[k++] = Array2[j++];
    }
	delete[] Array1;
    delete[] Array2;
}
int main() {
    int n[11] = {9, 30, 7, 49, 17, 21, 24, 4, 48, 23, 43};
    constantSpaceComplexity(n,0,sizeof(n) / sizeof(n[0])-1);
//    cout << "排序后的数组:";
 	for (int i = 0; i <11; ++i) {
        cout << n[i] << " ";
    }
    cout << endl;
    return 0;	
}

image.png

3. 三花聚顶 - 插入排序(Insertion Sort):插入排序每次将一个元素插入到已排序的数组中,形成有序序列,类似于三朵花瓣聚拢于顶端

将未排序的元素一个个插入到已排序的部分中的正确位置,通过比较和移动元素实现。逐个比较并插入,最终达到有序。

ezgif-4-cdd103a7be.gif

#include <iostream>

using namespace std;

void constantSpaceComplexity(int n[]) {
    for (int i = 0; i < 11; i++) {
    	int location = n[i];//当前的值
        // 在已排序的部分中找到合适的位置并将元素插入
        for (int j = i - 1; j >= 0; j--) {
            if (location < n[j]) {
                n[j + 1] = n[j]; // 将较大的元素后移
                n[j] = location; // 插入当前元素
            }
        }
    }
}
int main() {
    int n[11] = {9, 30, 7, 49, 17, 21, 24, 4, 48, 23, 43};
    constantSpaceComplexity(n); // 调用常数空间复杂度函数对数组进行排序
    cout << "排序后的数组:";
    for (int i = 0; i < 11; i++) {
        cout << n[i] << " ";
    }
    cout << endl;
    return 0;
}

4. 四面楚歌 - 冒泡排序(Bubble Sort):冒泡排序从起始位置开始,每次比较相邻元素并交换,如四面攻击敌人形成围困。

原理:通过相邻元素的比较和交换来将最大(或最小)的元素逐渐移动到最右(或最左)的位置。比较相邻元素交换,逐步将最大元素移至末尾。
复杂度:O(N^2)(n为数组的长度)

ezgif-2-ebd1f1bd0d.gif

#include <iostream>
using namespace std;
void constantSpaceComplexity(int n[]) {
    for (int i = 0; i < 11; i++) {
        for (int j = 0; j < 10 - i; j++) {//注意出现数组越界问题
            if (n[j] > n[j + 1]) {  // 比较相邻两个元素,如果顺序不正确则交换
                swap(n[j], n[j + 1]);
            }
        }
    }
}
int main() {
    int n[11] = {9, 30, 7, 49, 17, 21, 24, 4, 48, 23, 43};
    constantSpaceComplexity(n);
    cout << "排序后的数组:";
    for (int i = 0; i < 11; i++) {
        cout << n[i] << " ";
    }
    cout << endl;
    return 0;
}

5. 五云手 - 计数排序(Counting Sort):计数排序通过统计各个元素的个数,确定元素的相对位置,如五片剑雾迷惑对手。

原理
根据序列中每个元素的值,统计小于等于该元素值的元素个数,再根据统计信息将元素放置到正确的位置上。

ezgif-4-7ee2477822.gif

image.png

#include <iostream>

using namespace std;

void merge(int n[],  int len) {
	int max=0;
	int min=0;
	for(int i=0;i<len;i++){
		if(n[i]>max){
			max=n[i];
		}
		if(n[i]<min){
			min=n[i];
		}
	} 
	int* Array = new int[max];
	for(int i=0;i<=max;i++){
		Array[i]=0;
	} 
	for(int i=0;i<len;i++){	
		Array[n[i]]++;	
	} 
	int index=0;
	for(int i=0;i<=max;i++){	
		while(Array[i]){
			n[index++]=i;
			Array[i]--;
		}  
	} 
   delete[] Array;
}


int main() {
    int n[] = {5, 2, 2, 1, 4, 5, 2, 4, 1, 4};
    merge(n,  10);
//    cout << "排序后的数组:";
    for (int i = 0; i < 9; i++) {
        cout << n[i] << " ";
    }
    cout << endl;
    return 0;
}

6. 六法归宗 - 基数排序(Radix Sort):基数排序按照个位、十位、百位等顺序进行排序,将六种剑法融合成完美的剑招。

原理
根据元素的位数,将序列按照低位到高位的顺序进行排序,重复多次直到所有位都进行了排序。

ezgif-4-6720244cfc.gif

#include <iostream>
#include <string>
using namespace std;

void constantSpaceComplexity(int n[],int len) {
	int max=0;
	for(int i=0;i<length;i++){
		if(n[i]>max){
			max=n[i];//获取最大数119
		}
	}
	int digits=0;
	while (max != 0) {
        max /= 10;
        digits++;//获取最大数为几位数字119为三位置
    }
	int myArray[10][len+1]={0};
	int inedex=1;
	for(int j=1;j<=digits;j++){
		int a=0;
		for (int k = 0; k < len; k++) {
	        for (int e = 0; e < len+1; e++) {
	        	myArray[k][e]=0;//每次赋初始值
	        }
	    }
		for(int i=0;i<=len;i++){
                        //获取每位上的数字列如119分别进行第一次:119/1%10=9 myarray[9][a++]=119
                        //第二次:119/10%10=1 myarray[1][a++]=119
                        //第三次:119/100%10=1 myarray[1][a++]=119
			myArray[n[i]/inedex%10][a++]=n[i];
		}	
	    int mun=0;
	    for (int k = 0; k < len; k++) {
	        for (int e = 0; e < len+1; e++) {
	        	if(myArray[k][e]>0){
                                //进行重新排序
	        		n[mun++]=myArray[k][e];
				}
	        }
	    }
		inedex=inedex*10;
	}
}
int main() {
    int n[11] = {119, 30, 7, 49, 172, 21, 24, 4, 48, 23, 43};
    constantSpaceComplexity(n,10);
//    cout << "排序后的数组:";
 	for (int i = 0; i <11; ++i) {
        cout << n[i] << " ";
    }
    cout << endl;
    return 0;	
}

7. 七星冠月- 堆排序(Heap Sort):堆排序利用堆的数据结构,将最大或最小元素放在顶部,如七颗星星环绕攻击敌人堆

利用堆这种数据结构进行排序,首先将待排序的序列构建成一个大顶堆或小顶堆,然后依次将堆顶元素与最后一个元素交换并调整堆,重复这个过程直到所有元素都排序完成。

大致思路:

  1. 构建最大堆:首先将待排序的序列构建成一个最大堆。从最后一个非叶子节点开始,依次向上调整每个节点,使得每个父节点的值都大于其子节点的值。
  2. 调整堆结构:将堆顶元素(最大值)与最后一个元素交换位置,然后将交换后的堆的大小减1,再对堆顶元素进行调整,使得剩余的元素重新构成一个最大堆。
  3. 重复步骤2,直到堆的大小为1,排序完成。
#include <iostream>

using namespace std;

// 堆化函数,将以 i 为根节点的子树调整成大顶堆
void heapify(int arr[], int n, int i) {
    int largest = i;      // 初始化最大值为当前节点
    int left = 2 * i + 1; // 左子节点索引
    int right = 2 * i + 2; // 右子节点索引

    // 如果左子节点大于最大值,则更新最大值
    if (left < n && arr[left] > arr[largest]) {
        largest = left;
    }

    // 如果右子节点大于最大值,则更新最大值
    if (right < n && arr[right] > arr[largest]) {
        largest = right;
    }

    // 如果最大值不是当前节点,则交换最大值和当前节点的位置,然后继续对该子树进行堆化
    if (largest != i) {
        int temp = arr[i];
        arr[i] = arr[largest];
        arr[largest] = temp;
        heapify(arr, n, largest);
    }
}

// 堆排序函数
void heapSort(int arr[], int len) {
    // 构建大顶堆,从最后一个非叶子节点开始进行堆化操作
    for (int i = len / 2 - 1; i >= 0; i--) {
        heapify(arr, len, i);
    }

    // 依次将堆顶元素(最大值)与末尾元素交换,并对减少了末尾元素的堆进行堆化操作
    for (int i = len - 1; i >= 0; i--) {
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;
        heapify(arr, i, 0);
    }
}

int main() {
    int arr[] = {119, 30, 7, 49, 172, 21, 24, 4, 48, 23, 43};
    int len = sizeof(arr) / sizeof(arr[0]);

    heapSort(arr, len);

    cout << "排序后的数组:";
    for (int i = 0; i < len; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;

    return 0;
}

8. 八方风雨 - 希尔排序(Shell Sort):希尔排序按照一定的间隔进行插入排序,逐渐减小间隔直至为1,如八个方向同时攻击对手。

原理:将序列按照一定的间隔分组进行插入排序,不断缩小间隔直至为1,最后再进行一次插入排序

第一次循环过程
image.png

希尔排序的增量可以使用两种常见的方式:
1.gap = [gap / 3] + 1:将初始增量设为数组长度的三分之一加一的值。在每一趟排序中,将当前增量按照这个公式重新计算。这种方式通常能够获得较好的排序性能。
gap = n/2:将初始增量设为数组长度的一半。在每一趟排序中,将当前增量减半。这种方式是最简单和直观的,但并不一定能够获得最优的性能。

#include <iostream>
#include <string>
using namespace std;

void shellSort(int n[], int len) {
    int gap = len;
    while (gap > 1) {
        gap = gap / 3 + 1; // 使用动态间隔序列,根据Knuth提出的间隔序列计算间隔
        for (int i = gap; i < len; ++i) {
            int temp = n[i]; // 当前待插入的元素
            int j = i - gap; // 前一个间隔位置的索引
            while (j >= 0 && n[j] > temp) { // 在当前间隔内进行插入排序
                n[j + gap] = n[j]; // 向后移动元素
                j -= gap; // 前一个间隔位置
            }
            n[j + gap] = temp; // 插入待排序元素到正确位置
        }
    }
}

int main() {
    int n[11] = {119, 30, 7, 49, 172, 21, 24, 4, 48, 23, 43};
    shellSort(n, 11); // 调用希尔排序函数对数组进行排序
    cout << "排序后的数组:";
    for (int i = 0; i < 11; ++i) {
        cout << n[i] << " "; // 输出排序后的数组元素
    }
    cout << endl;
    return 0;
}

我在17行下插入循环插件看每次循环结果:

image.png

9. 九转雷动-桶排序(Bucket Sort):桶排序将元素按照一定的范围划分到不同的桶中

原理:将待排序序列划分为若干个桶,每个桶内的元素进行排序,然后按照桶的顺序依次将元素取出,形成有序序列。

ec278825343644759a2fba2b0a878f20.gif

#include <iostream> 
#include <vector>     // 包含向量库
using namespace std;

void bucketSort(int arr[], int len) {
    int minValue = arr[0];   // 初始化最小值为数组的第一个元素
    int maxValue = arr[0];   // 初始化最大值为数组的第一个元素
    for (int i = 1; i < len; i++) {   // 遍历数组找到最小值和最大值
        if (arr[i] < minValue) {
            minValue = arr[i];
        }
        if (arr[i] > maxValue) {
            maxValue = arr[i];
        }
    }
    int bucketCount = (maxValue - minValue) / len + 1;   // 根据最小值和最大值计算桶的数量
    vector<vector<int>> buckets(bucketCount);   // 创建一个二维向量,表示桶
    for (int i = 0; i < len; i++) {   // 遍历数组中的每个元素
        int bucketIndex = (arr[i] - minValue) / len;   // 计算元素所属的桶的索引
        buckets[bucketIndex].push_back(arr[i]);   // 将元素添加到对应的桶中
    }
    
    // 对每个桶进行排序并合并结果
    int index = 0;
    for (int i = 0; i < bucketCount; i++) {   // 遍历每个桶
        sort(buckets[i].begin(), buckets[i].end());   // 对桶中的元素进行排序
        for (int j = 0; j < buckets[i].size(); j++) {   // 遍历桶中的元素
            arr[index++] = buckets[i][j];   // 将桶中的元素依次放入原数组中
        }
    }
}

int main() {
    int n[11] = {119, 30, 7, 49, 172, 21, 24, 4, 48, 23, 43};   // 定义一个包含11个元素的整型数组并初始化
    bucketSort(n, 11);   
    
    cout << "排序后的数组:";  
    for (int i = 0; i < 11; ++i) {   
        cout << n[i] << " ";   
    }
    cout << endl;   // 换行
    
    return 0;  
}

第一次排序image.png

10. 选择排序(Selection Sort)- 十殿阎罗:选择排序每次从未排序部分选择最小(或最大)的元素放到已排序部分的末尾,类似于十种剑法中选择合适的一招。

原理:每次从未排序的部分选择最小(或最大)的元素,并与未排序部分的第一个元素进行交换,将最小(或最大)元素放到已排序部分的末尾。

选择最小(或最大),交换位置,逐步构建有序。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nv2AsiCK-1688630791229)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/00d44854f86747ceb5a1c8fcc1a65902~tplv-k3u1fbpfcp-watermark.image?)]

#include <iostream>

using namespace std;

void constantSpaceComplexity(int n[]) {
    for (int i = 0; i < 11; i++) {
    	int location=i;
         // 在剩余的元素中找到最小值的位置
        for (int j = i+1; j < 11; j++) {
            if (n[location] > n[j]) { 
				location=j; 
            }
        }
        // 将最小值交换到当前位置
        swap(n[i], n[location]);
    }
}
int main() {
    int n[11] = {9, 30, 7, 49, 17, 21, 24, 4, 48, 23, 43};
    constantSpaceComplexity(n);
    cout << "排序后的数组:";
    for (int i = 0; i < 11; i++) {
        cout << n[i] << " ";
    }
    cout << endl;
    return 0;
}

在这10大招式中比较难理解的有:

根据常见的观点和经验一些相对较难理解的排序算法:(我会根据反馈细讲难点)

序号排序算法难度描述
1归并排序使用递归和分治思想,初学者可能觉得抽象和复杂。
2希尔排序是插入排序的一种变种,涉及到对不断缩小的子序列进行排序,分组和交换的过程较难理解。
4堆排序利用二叉堆的性质进行排序,包括建堆、调整堆和堆化等操作,这些操作对初学者来说可能有一定的挑战性。
5桶排序将元素划分到不同的桶中,并对每个桶进行排序,然后按顺序合并各个桶的结果,这种分桶和合并的过程对初学者来说可能较难理解。

image.png
注:掘金原文为本人账号

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值