八大排序 整理

八大排序整理与比较

(参考大佬链接 https://blog.csdn.net/c406495762/article/details/78979946

è¿éåå¾çæè¿°

 

 

1.选择排序

           每趟从待排序的记录中选出关键字最小的记录,顺序放在已排序的记录序列末尾,直到全部排序结束为止。

(1)简单选择排序

#include <iostream>
#include <vector>

using namespace std;

// 简单选择排序
vector<int> SelectionSort(vector<int> list) 
{
    // 需要遍历获得最小值的次数
    // 要注意一点,当要排序 N 个数,已经经过 N-1 次遍历后,已经是有序数列
    vector<int> result = list;
    for (int i = 0; i < result.size(); i++) {
        // 用来保存最小值得索引
        int index = i;
        // 用来保存最小值得索引
        for (int j = i + 1; j < result.size(); j++) {
            if (result[index] > result[j]) {
                index = j;
            }
        }
        if (index == i) {
            continue;
        }
        // 将找到的第i个小的数值放在第i个位置上
        swap(result[i], result[index]);
        cout << "第" << i + 1 << "趟:\t";
        for (int i = 0; i < result.size(); i++) {
            cout << result[i] << " ";
        }
        cout << endl;
    }
    return result;
}

int main() {
    int arr[] = { 6, 4, 8, 9, 2, 3, 1 };
    vector<int> test(arr, arr + sizeof(arr) / sizeof(arr[0]));  //vector <T> c(beg,end) 以区间赋值
    cout << "排序前" << endl;
    for (int i = 0; i < test.size(); i++) {
        cout << test[i] << " ";
    }
    cout << endl;
    vector<int> result;
    result = SelectionSort(test);
    cout << "排序后" << endl;
    for (int i = 0; i < result.size(); i++) {
        cout << result[i] << " ";
    }
    cout << endl;

    return 0;
}

 

(2)堆排序

排序步骤:

       1.用原数组构建一个完全二叉树

       2.调整大小 确保所有的父节点的值比自己的子节点的值都要大(小)

       3.交换第一个和最后一个元素位置 输出最后一个元素:当前的最大值(最小值)

       4.重复2.3两个步骤直至所有元素都被输出 完成排序

大佬那参考的图解:

代码

#include <iostream>
#include <vector>

using namespace std;

void HeapAdjust(vector<int> &list, int parent, int length) {
    int temp = list[parent];                    // temp保存当前父节点
    int child = 2 * parent + 1;                    // 先获得左孩子

    while (child < length) {
        // 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点
        if (child + 1 < length && list[child] < list[child + 1]) {
            child++;
        }

        // 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点
        if (temp >= list[child]) {
            break;
        }

        // 把孩子结点的值赋给父结点
        list[parent] = list[child];

        // 选取孩子结点的左孩子结点,继续向下筛选
        parent = child;
        child = 2 * parent + 1;
    }
    list[parent] = temp;
}

vector<int> HeadSort(vector<int> list) {
    int length = list.size();
    // 循环建立初始堆
    for (int i = length / 2 - 1; i >= 0; i--) {
        HeapAdjust(list, i, length);
    }

    // 进行n-1次循环,完成排序
    for (int i = length - 1; i > 0; i--) {
        // 最后一个元素和第一元素进行交换
        int temp = list[i];
        list[i] = list[0];
        list[0] = temp;

        // 筛选 R[0] 结点,得到i-1个结点的堆
        HeapAdjust(list, 0, i);
        cout << "第" << length - i << "趟排序:";
        for (int i = 0; i < list.size(); i++) {
            cout << list[i] << " ";
        }
        cout << endl;
    }
    return list;
}

int main() {
    int arr[] = { 15, 32, 24, 91, 48, 75, 81 };
    vector<int> test(arr, arr + sizeof(arr) / sizeof(arr[0]));
    cout << "排序前:";
    for (int i = 0; i < test.size(); i++) {
        cout << test[i] << " ";
    }
    cout << endl;
    vector<int> result;
    result = HeadSort(test);
    cout << "排序后:";
    for (int i = 0; i < result.size(); i++) {
        cout << result[i] << " ";
    }
    cout << endl;
    
    return 0;
}

 

 

2.交换排序

(1)冒泡排序

       重复地走访要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端,故名冒泡排序。

        假设有一个大小为 N 的无序序列。以升序冒泡排序为例,冒泡排序就是要每趟排序过程中通过两两比较相邻元素,将小的数字放到前面,大的数字放在后面。

        个人觉得冒泡排序和简单选择排序肯定是不一样的,有些人经常认为选择排序就是冒泡排序或者根本不知道选择排序,其实区分方法很简单,选择排序在排序时是把最大或最小值从待排序列中选出来,而冒泡是通过相邻元素两两比较把待排序列的最大或最小值推出来,有点想象力的应该都分得清这两者的区别。

代码:

#include <iostream>
#include <vector>

using namespace std;

vector<int> bubbleSort(vector<int> list) {
    vector<int> result;
    if (list.empty()) {
        return result;
    }

    result = list;
    int temp;
    // 要遍历的次数
    for (int i = 0; i < result.size() - 1; ++i) {
        cout << "第" << i + 1 << "趟排序:" << endl;;
        // 从后向前依次的比较相邻两个数的大小
        for (int j = 0; j < result.size() - 1; j++) {
            // 如果后面的元素小,则交换它们的位置
            if (result[j + 1] < result[j]) {
                temp = result[j + 1];
                result[j + 1] = result[j];
                result[j] = temp;
            }
            cout << "排序中:";
            for (int s = 0; s < result.size(); s++) {
                cout << result[s] << " ";
            }
            cout << endl;
        }
        cout << "排序结果:";
        for (int s = 0; s < result.size(); s++) {
            cout << result[s] << " ";
        }
        cout << endl;
    }
    return result;
}

int main() {
    int arr[] = { 6, 4, 8, 1, 2, 3, 9 };
    vector<int> test(arr, arr + sizeof(arr) / sizeof(arr[0]));
    cout << "排序前" << endl;
    for (int i = 0; i < test.size(); i++) {
        cout << test[i] << " ";
    }
    cout << endl;
    vector<int> result;
    result = bubbleSort(test);
    cout << "排序后" << endl;
    for (int i = 0; i < result.size(); i++) {
        cout << result[i] << " ";
    }
    cout << endl;
    return 0;
}

 

 

(2)快速排序

        快速排序的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分:分割点左边都是比它小的数,右边都是比它大的数。

        然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

         在快速排序中,相等元素可能会因为分区而交换顺序,所以它是不稳定的算法。

大佬的图解:

 

上图中,演示了快速排序的处理过程:

初始状态为一组无序的数组:2、4、5、1、3。

经过以上操作步骤后,完成了第一次的排序,得到新的数组:1、2、5、4、3。

新的数组中,以2为分割点,左边都是比2小的数,右边都是比2大的数。

因为2已经在数组中找到了合适的位置,所以不用再动。

2左边的数组只有一个元素1,所以显然不用再排序,位置也被确定。(注:这种情况时,left指针和right指针显然是重合的。因此在代码中,我们可以通过设置判定条件left必须小于right,如果不满足,则不用排序了)。

而对于2右边的数组5、4、3,设置left指向5,right指向3,开始继续重复图中的一、二、三、四步骤,对新的数组进行排序。

代码:

#include <iostream>
#include <vector>

using namespace std;

int division(vector<int> &list, int left, int right) {
    // 以最左边的数(left)为基准
    int base = list[left];
    while (left < right) {
        // 从序列右端开始,向左遍历,直到找到小于base的数
        while (left < right && list[right] >= base)
            right--;
        // 找到了比base小的元素,将这个元素放到最左边的位置
        list[left] = list[right];

        // 从序列左端开始,向右遍历,直到找到大于base的数
        while (left < right && list[left] <= base)
            left++;
        // 找到了比base大的元素,将这个元素放到最右边的位置
        list[right] = list[left];
    }

    // 最后将base放到left位置。此时,left位置的左侧数值应该都比left小;
    // 而left位置的右侧数值应该都比left大。
    list[left] = base;
    return left;
}

// 快速排序
void QuickSort(vector<int> &list, int left, int right) {
    // 左下标一定小于右下标,否则就越界了
    if (left < right) {
        // 对数组进行分割,取出下次分割的基准标号
        int base = division(list, left, right);

        // 对“基准标号“左侧的一组数值进行递归的切割,以至于将这些数值完整的排序
        QuickSort(list, left, base - 1);

        // 对“基准标号“右侧的一组数值进行递归的切割,以至于将这些数值完整的排序
        QuickSort(list, base + 1, right);
    }
}

int main() {
    int arr[] = { 6, 4, 8, 9, 2, 3, 1 };
    vector<int> test(arr, arr + sizeof(arr) / sizeof(arr[0]));
    cout << "排序前" << endl;
    for (int i = 0; i < test.size(); i++) {
        cout << test[i] << " ";
    }
    cout << endl;
    vector<int> result = test;
    QuickSort(result, 0, result.size() - 1);
    cout << "排序后" << endl;
    for (int i = 0; i < result.size(); i++) {
        cout << result[i] << " ";
    }
    cout << endl;
    return 0;
}

 

 

3.插入排序

(1)直接插入排序

         每次将一个待排序的记录,按照其关键字的大小插入到有序队列的合适位置里,直至全部插入完成。 

        (1). 我们先将这个序列中下标为 0 的元素视为元素个数为 1 的有序序列。

        (2) .然后,我们要依次把 R1, R2, ... , RN-1 插入到这个有序序列中。所以,我们需要一个外部循环,从下标 1 扫描到 N-1 。

        (3) .接下来描述插入过程。假设这是要将 Ri 插入到前面有序的序列中。由前面所述,我们可知,插入Ri时,前 i-1 个数肯定已经是有序了。所以我们需要将Ri 和R0 ~ Ri-1 进行比较,确定要插入的合适位置。这就需要一个内部循环,我们一般是从后往前比较,即从下标 i-1 开始向 0 进行扫描。

 

代码:

 

#include <iostream>
#include <vector>

using namespace std;

vector<int> insertSort(vector<int> list) {
    vector<int> result;
    if (list.empty()) {
        return result;
    }
    result = list;
    // 第1个数肯定是有序的,从第2个数开始遍历,依次插入有序序列
    for (int i = 1; i < result.size(); i++) {
        // 取出第i个数,和前i-1个数比较后,插入合适位置
        int temp = result[i];
        // 因为前i-1个数都是从小到大的有序序列,所以只要当前比较的数(list[j])比temp大,就把这个数后移一位
        int j = i - 1;
        for (j; j >= 0 && result[j] > temp; j--) {
            result[j + 1] = result[j];
        }
        result[j + 1] = temp;
    }
    return result;
}

int main() {
    int arr[] = { 11, 8, 75, 45, 26, 37, 0 };
    vector<int> test(arr, arr + sizeof(arr) / sizeof(arr[0]));
    cout << "排序前" << endl;
    for (int i = 0; i < test.size(); i++) {
        cout << test[i] << " ";
    }
    cout << endl;
    vector<int> result;
    result = insertSort(test);
    cout << "排序后" << endl;
    for (int i = 0; i < result.size(); i++) {
        cout << result[i] << " ";
    }
    cout << endl;
    return 0;
}

 

 

(2)希尔排序

          希尔(Shell)排序又称为缩小增量排序,它是一种插入排序。它是直接插入排序算法的一种威力加强版

          算法最开始以一定的步长进行排序,然后会继续以更小的步长进行排序,最终算法以步长为 1 进行排序。当步长为 1 时,算法变为直接插入排序,这就保证了数据一定会被全部排序。

代码:

#include <iostream>
#include <vector>

using namespace std;

vector<int> ShellSort(vector<int> list) {
    vector<int> result = list;
    int n = result.size();
    for (int gap = n >> 1; gap > 0; gap >>= 1) {
        for (int i = gap; i < n; i++) {
            int temp = result[i];
            int j = i - gap;
            while (j >= 0 && result[j] > temp) {
                result[j + gap] = result[j];
                j -= gap;
            }
            result[j + gap] = temp;
        }
        for (int i = 0; i < result.size(); i++) {
            cout << result[i] << " ";
        }
        cout << endl;
    }
    return result;
}

int main() {
    int arr[] = { 6, 4, 8, 9, 2, 3, 1 };
    vector<int> test(arr, arr + sizeof(arr) / sizeof(arr[0]));
    cout << "排序前" << endl;
    for (int i = 0; i < test.size(); i++) {
        cout << test[i] << " ";
    }
    cout << endl;
    vector<int> result;
    result = ShellSort(test);
    cout << "排序后" << endl;
    for (int i = 0; i < result.size(); i++) {
        cout << result[i] << " ";
    }
    cout << endl;
    return 0;
}

 

 

4.归并排序

           归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer的一个非常典型的应用。

          (1)分阶段

          (2)治阶段

大佬的图解:

 

代码:

#include <iostream>
#include <vector>

using namespace std;

void Merge(vector<int> &input, int left, int mid, int right, vector<int> temp) {
    int i = left;                // i是第一段序列的下标
    int j = mid + 1;            // j是第二段序列的下标
    int k = 0;                    // k是临时存放合并序列的下标

    // 扫描第一段和第二段序列,直到有一个扫描结束
    while (i <= mid && j <= right) {
        // 判断第一段和第二段取出的数哪个更小,将其存入合并序列,并继续向下扫描
        if (input[i] <= input[j]) {
            temp[k++] = input[i++];
        }
        else {
            temp[k++] = input[j++];
        }
    }
    // 若第一段序列还没扫描完,将其全部复制到合并序列
    while (i <= mid) {
        temp[k++] = input[i++];
    }

    // 若第二段序列还没扫描完,将其全部复制到合并序列
    while (j <= right) {
        temp[k++] = input[j++];
    }

    k = 0;
    // 将合并序列复制到原始序列中
    while (left <= right) {
        input[left++] = temp[k++];
    }
}

void MergeSort(vector<int> &input, int left, int right, vector<int> temp) {
    if (left < right) {
        int mid = (right + left) >> 1;
        MergeSort(input, left, mid, temp);
        MergeSort(input, mid + 1, right, temp);
        Merge(input, left, mid, right, temp);
    }
}

void mergesort(vector<int> &input) {
    // 在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
    vector<int> temp(input.size());
    MergeSort(input, 0, input.size() - 1, temp);
}

int main() {
    int arr[] = { 18, 42, 76, 55, 20, 12, 96 };
    vector<int> test(arr, arr + sizeof(arr) / sizeof(arr[0]));
    cout << "排序前:";
    for (int i = 0; i < test.size(); i++) {
        cout << test[i] << " ";
    }
    cout << endl;

    vector<int> result = test;
    mergesort(result);
    cout << "排序后:";
    for (int i = 0; i < result.size(); i++) {
        cout << result[i] << " ";
    }
    cout << endl;
    return 0;
}

 

 

5.基数排序

        基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

        基本思想:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

算法步骤:

  • 将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。
  • 从最低位开始,依次进行一次排序。
  • 这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

代码:

#include <iostream>
#include <vector>

using namespace std;

// 求出数组中最大数的位数的函数
int MaxBit(vector<int> input) {
    // 数组最大值
    int max_data = input[0];
    for (int i = 1; i < input.size(); i++) {
        if (input[i] > max_data) {
            max_data = input[i];
        }
    }

    // 数组最大值的位数
    int bits_num = 0;
    while (max_data) {
        bits_num++;
        max_data /= 10;
    }

    return bits_num;
}

// 取数xxx上的第d位数字
int digit(int num, int d) {
    int pow = 1;
    while (--d > 0) {
        pow *= 10;
    }
    return num / pow % 10;
}

// 基数排序
vector<int> RadixSort(vector<int> input, int n) {
    // 临时数组,用来存放排序过程中的数据
    vector<int> bucket(n);
    // 位记数器,从第0个元素到第9个元素依次用来记录当前比较位是0的有多少个...是9的有多少个数
    vector<int> count(10);
    // 从低位往高位循环
    for (int d = 1; d <= MaxBit(input); d++) {
        // 计数器清0
        for (int i = 0; i < 10; i++) {
            count[i] = 0;
        }

        // 统计各个桶中的个数
        for (int i = 0; i < n; i++) {
            count[digit(input[i], d)]++;
        }

        /*
        * 比如某次经过上面统计后结果为:[0, 2, 3, 3, 0, 0, 0, 0, 0, 0]则经过下面计算后 结果为: [0, 2,
        * 5, 8, 8, 8, 8, 8, 8, 8]但实质上只有如下[0, 2, 5, 8, 0, 0, 0, 0, 0, 0]中
        * 非零数才用到,因为其他位不存在,它们分别表示如下:2表示比较位为1的元素可以存放在索引为1、0的
        * 位置,5表示比较位为2的元素可以存放在4、3、2三个(5-2=3)位置,8表示比较位为3的元素可以存放在
        * 7、6、5三个(8-5=3)位置
        */
        for (int i = 1; i < 10; i++) {
            count[i] += count[i - 1];
        }

        /*
        * 注,这里只能从数组后往前循环,因为排序时还需保持以前的已排序好的顺序,不应该打
        * 乱原来已排好的序,如果从前往后处理,则会把原来在前面会摆到后面去,因为在处理某个
        * 元素的位置时,位记数器是从大到到小(count[digit(arr[i], d)]--)的方式来处
        * 理的,即先存放索引大的元素,再存放索引小的元素,所以需从最后一个元素开始处理。
        * 如有这样的一个序列[212,213,312],如果按照从第一个元素开始循环的话,经过第一轮
        * 后(个位)排序后,得到这样一个序列[312,212,213],第一次好像没什么问题,但问题会
        * 从第二轮开始出现,第二轮排序后,会得到[213,212,312],这样个位为3的元素本应该
        * 放在最后,但经过第二轮后却排在了前面了,所以出现了问题
        */
        for (int i = n - 1; i >= 0; i--) {
            int k = digit(input[i], d);
            bucket[count[k] - 1] = input[i];
            count[k]--;
        }

        // 临时数组复制到 input 中
        for (int i = 0; i < n; i++) {
            input[i] = bucket[i];
        }
    }

    return input;
}

int main() {
    int arr[] = { 50, 123, 543, 187, 49, 30, 0, 2, 11, 100 };
    vector<int> test(arr, arr + sizeof(arr) / sizeof(arr[0]));
    cout << "排序前:";
    for (int i = 0; i < test.size(); i++) {
        cout << test[i] << " ";
    }
    cout << endl;

    vector<int> result = test;
    result = RadixSort(result, result.size());
    cout << "排序后:";
    for (int i = 0; i < result.size(); i++) {
        cout << result[i] << " ";
    }
    cout << endl;
    return 0;
}

 

 

最后附上参考来的整理

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值