碧血剑十式:《碧血剑》是金庸先生创作的武侠小说之一,主角血刀老祖所掌握的剑法,共有十种不同的剑式,每一式都有各自独特的特点。
如果将学习算法的过程比喻成武侠小说,排序算法可以看作是基础功夫的阶段。
排序排名(简入难)
序号 | 排序算法 | 空间复杂度 | 时间复杂度 |
---|---|---|---|
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):快速排序以一次划分为基准,将数据分成两部分,类似一剑斩断三人
原理 | |
---|---|
通过选择一个基准元素,将序列划分为两个子序列,其中一个子序列的元素都小于等于基准元素,另一个子序列的元素都大于等于基准元素,然后对两个子序列递归地进行快速排序。(难点) |
总结:选择基准,划分数组,递归排序,合并子数组,最终得到排序后的数组。
注重先找右边最小值!!!
#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):归并排序将两个有序的子数组合并成一个有序数组,如同两仙较量长短。
原理 | |
---|---|
将序列划分为较小的子序列,递归地对子序列进行排序,然后将排好序的子序列合并成一个更大的有序序列。 |
简单解析:
- 第一次9, 30, 7, 49, 17, 21,分为两组,两组9,30与7,49都符合从小到大
- 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;
}
3. 三花聚顶 - 插入排序(Insertion Sort):插入排序每次将一个元素插入到已排序的数组中,形成有序序列,类似于三朵花瓣聚拢于顶端
将未排序的元素一个个插入到已排序的部分中的正确位置,通过比较和移动元素实现。逐个比较并插入,最终达到有序。
#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为数组的长度)
#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):计数排序通过统计各个元素的个数,确定元素的相对位置,如五片剑雾迷惑对手。
原理 | |
---|---|
根据序列中每个元素的值,统计小于等于该元素值的元素个数,再根据统计信息将元素放置到正确的位置上。 |
#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):基数排序按照个位、十位、百位等顺序进行排序,将六种剑法融合成完美的剑招。
原理 | |
---|---|
根据元素的位数,将序列按照低位到高位的顺序进行排序,重复多次直到所有位都进行了排序。 |
#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,排序完成。
#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,最后再进行一次插入排序
第一次循环过程
希尔排序的增量可以使用两种常见的方式:
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行下插入循环插件看每次循环结果:
9. 九转雷动-桶排序(Bucket Sort):桶排序将元素按照一定的范围划分到不同的桶中
原理:将待排序序列划分为若干个桶,每个桶内的元素进行排序,然后按照桶的顺序依次将元素取出,形成有序序列。
#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;
}
第一次排序
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 | 桶排序 | 将元素划分到不同的桶中,并对每个桶进行排序,然后按顺序合并各个桶的结果,这种分桶和合并的过程对初学者来说可能较难理解。 |
注:掘金原文为本人账号