七大排序算法

插入排序(Insertion Sort)

插入排序(Insertion sort) 是一种简单直观的排序算法。

它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

插入排序的算法步骤如下:

  1. 从第一个元素开始,该元素可以认为已经被排序;

  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;

  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;

  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;

  5. 将新元素插入到该位置后;

  6. 重复步骤2~5。

  7. 图示如下:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    void insertionSort(vector<int> &a)
    {
        int len = a.size();  // 获取数组的长度
        for (int i = 0, j, temp; i < len - 1; i++) // 需要循环 len-1 次,因为最后一个元素不需要再排序
        {
            j = i;                 // 将 j 初始化为 i,这样 j 可以从当前元素向前比较
            temp = a[i + 1];       // temp 保存当前要插入的元素,也就是下一个要排序的元素
            while (j >= 0 && a[j] > temp)  // 如果 j >= 0 并且 a[j] 大于 temp,则需要将 a[j] 向后移动一位
            {
                a[j + 1] = a[j];   // 将 a[j] 移动到 a[j + 1],腾出位置给 temp 插入
                j--;               // j 向前移动,继续比较前面的元素
            }
            a[j + 1] = temp;       // 当退出循环时,a[j] 已经不大于 temp,将 temp 插入到正确位置
        }
    }
    
    

冒泡排序(Bubble Sort)

冒泡排序(Bubble Sort) 是一种基础的 交换排序

冒泡排序之所以叫冒泡排序,是因为它每一种元素都像小气泡一样根据自身大小一点一点往数组的一侧移动。

算法步骤如下:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个;
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数;
  3. 针对所有的元素重复以上的步骤,除了最后一个;
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

图示如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

void bubbleSort(vector<int> &a)
{
    int len = a.size();
    for (int i = 0; i < len - 1; i++) //需要循环次数
    {
        for (int j = 0; j < len - 1 - i; j++) //每次需要比较个数
        {
            if (a[j] > a[j + 1])
            {
                swap(a[j], a[j + 1]); //不满足偏序,交换
            }
        }
    }
}

选择排序(Selection Sort)

选择排序(Selection sort) 是一种简单直观的排序算法。

选择排序的主要优点与数据移动有关。

如果某个元素位于正确的最终位置上,则它不会被移动。

选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对 n 个元素的表进行排序总共进行至多 n - 1 次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。

选择排序的算法步骤如下:

  1. 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置;

  2. 然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾;

  3. 以此类推,直到所有元素均排序完毕。

  4. 图示如下:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    void selectionSort(vector<int> &a)
    {
        int len = a.size();  // 获取数组的长度
        for (int i = 0, minIndex; i < len - 1; i++) // 需要循环 len-1 次,因为最后一个元素不需要再排序
        {
            minIndex = i;  // 将 minIndex 初始化为当前 i,即当前循环开始时假设最小元素为当前位置 i
            for (int j = i + 1; j < len; j++) // 遍历未排序部分,从 i+1 到数组末尾
            {
                if (a[j] < a[minIndex]) // 如果找到比当前最小元素更小的元素
                    minIndex = j; // 更新最小元素的下标为 j
            }
            swap(a[i], a[minIndex]); // 将找到的最小元素与当前位置 i 进行交换
        }
    }
    
    

快速排序(Quick Sort)

快速排序(Quicksort),又称 划分交换排序(partition-exchange sort)

快速排序(Quicksort) 在平均状况下,排序 n 个项目要 O(n log n) 次比较。在最坏状况下则需要 O(n2) 次比较,但这种状况并不常见。事实上,快速排序 O(n log n) 通常明显比其他算法更快,因为它的 内部循环(inner loop) 可以在大部分的架构上很有效率地达成。

快速排序使用 分治法(Divide and conquer) 策略来把一个序列分为较小和较大的2个子序列,然后递归地排序两个子序列。

快速排序的算法步骤如下:

  1. 挑选基准值:从数列中挑出一个元素,称为 “基准”(pivot)
  2. 分割:重新排序序列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,对基准值的排序就已经完成;
  3. 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。

递归到最底部的判断条件是序列的大小是零或一,此时该数列显然已经有序。

选取基准值有数种具体方法,此选取方法对排序的时间性能有决定性影响。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

int partition(vector<int> &a, int left, int right)
{
    int pivot = a[right];
    int i = left - 1;
    for (int j = left; j < right; j++)
    {
        if (a[j] <= pivot)
        {
            i++;
            swap(a[i], a[j]);
        }
    }
    swap(a[i + 1], a[right]);
    return i + 1;
}

void quickSort(vector<int> &a, int left, int right)
{
    if (left < right)
    {
        int mid = partition(a, left, right);
        quickSort(a, left, mid - 1);
        quickSort(a, mid + 1, right);
    }
}

void qSort(vector<int> &a)
{
    quickSort(a, 0, a.size() - 1);
}

我这里用了递归写法,非递归也很简单,就是比较哪个叶子节点大,再继续for下去

希尔排序(Shell Sort)

希尔排序,也称 递减增量排序算法,是 插入排序 的一种更高效的改进版本。希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  1. 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到 线性排序 的效率;
  2. 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。

步长的选择是希尔排序的重要部分。

只要最终步长为1任何步长序列都可以工作。

算法最开始以一定的步长进行排序。

然后会继续以一定步长进行排序,最终算法以步长为1进行排序。

当步长为1时,算法变为普通插入排序,这就保证了数据一定会被排序。

希尔排序的算法步骤如下:

  1. 定义一个用来分割的步长;
  2. 按步长的长度K,对数组进行K趟排序;
  3. 不断重复上述步骤。
img
void shell_Sort(vector<int> &a)
{
    int len = a.size();
    for (int gap = len / 2; gap > 0; gap /= 2)
    {
        for (int i = 0; i < gap; i++)
        {
            for (int j = i + gap, temp, preIndex; j < len; j = j + gap) //依旧需要temp作为哨兵
            {
                temp = a[j];        //保存哨兵
                preIndex = j - gap; //将要对比的编号
                while (preIndex >= 0 && a[preIndex]>temp)
                    {
                        a[preIndex + gap] = a[preIndex]; //被替换
                        preIndex -= gap;                 //向下走一步
                    }
                a[preIndex + gap] = temp; //恢复被替换的值
            }
        }
    }
}

堆排序(Heap Sort)

堆排序(Heapsort) 是指利用 二叉堆 这种数据结构所设计的一种排序算法。堆是一个近似 完全二叉树 的结构,并同时满足 堆积的性质 :即子节点的键值或索引总是小于(或者大于)它的父节点。

二叉堆是什么?

二叉堆分以下两个类型:

1.最大堆:最大堆任何一个父节点的值,都大于等于它左右孩子节点的值。

img

2.最小堆:最小堆任何一个父节点的值,都小于等于它左右孩子节点的值。

同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子

img

该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是:

大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]

小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

堆排序的算法步骤如下:

  1. 把无序数列构建成二叉堆;
  2. 循环删除堆顶元素,替换到二叉堆的末尾,调整堆产生新的堆顶。
void adjustHeap(vector<int> &a, int i,int len)
{
    int maxIndex = i;
    //如果有左子树,且左子树大于父节点,则将最大指针指向左子树
    if (i * 2 + 1 < len && a[i * 2 + 1] > a[maxIndex])
        maxIndex = i * 2 + 1;
    //如果有右子树,且右子树大于父节点和左节点,则将最大指针指向右子树
    if (i * 2 + 2 < len && a[i * 2 + 2] > a[maxIndex])
        maxIndex = i * 2 + 2;
    //如果父节点不是最大值,则将父节点与最大值交换,并且递归调整与父节点交换的位置。
    if (maxIndex != i)
    {
        swap(a[maxIndex], a[i]);
        adjustHeap(a, maxIndex,len);
    }
}

void Sort(vector<int> &a)
{
    int len = a.size();
    //1.构建一个最大堆
    for (int i = len / 2 - 1; i >= 0; i--) //从最后一个非叶子节点开始
    {
        adjustHeap(a, i,len);
    }
    //2.循环将堆首位(最大值)与末位交换,然后在重新调整最大堆

    for (int i = len - 1; i > 0; i--)
    {
        swap(a[0], a[i]);
        adjustHeap(a, 0, i);
    }
}

归并排序(Merge Sort)

归并排序(Merge sort) ,是创建在归并操作上的一种有效的排序算法,时间复杂度为 O(n log n) 。1945年由约翰·冯·诺伊曼首次提出。该算法是采用 分治法(Divide and Conquer) 的一个非常典型的应用,且各层分治递归可以同时进行。

(分治法将问题(divide)成一些小的问题然后递归求解,而**治(conquer)**的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

其实说白了就是将两个已经排序的序列合并成一个序列的操作。

img

并归排序有两种实现方式

img

第一种是 自上而下的递归 ,算法步骤如下:

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
  4. 重复步骤3直到某一指针到达序列尾;
  5. 将另一序列剩下的所有元素直接复制到合并序列尾。

具体代码:

#include <vector>
#include <iostream>

// 合并函数,用于合并两个子数组
void merge(std::vector<int>& arr, std::vector<int>& temp, int left, int mid, int right) {
    int i = left;     // 左子数组的起始索引
    int j = mid + 1;  // 右子数组的起始索引
    int k = left;     // 临时数组的起始索引

    // 遍历两个子数组,选择较小的元素放到临时数组中
    while (i <= mid && j <= right) {
        if (arr[i] <= arr[j]) {
            temp[k++] = arr[i++];
        } else {
            temp[k++] = arr[j++];
        }
    }

    // 复制左子数组剩余的元素到临时数组中
    while (i <= mid) {
        temp[k++] = arr[i++];
    }

    // 复制右子数组剩余的元素到临时数组中
    while (j <= right) {
        temp[k++] = arr[j++];
    }

    // 将排序后的元素复制回原数组
    for (i = left; i <= right; i++) {
        arr[i] = temp[i];
    }
}

// 归并排序函数
void mergeSort(std::vector<int>& arr, std::vector<int>& temp, int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2; // 计算中点

        // 递归排序左半部分
        mergeSort(arr, temp, left, mid);

        // 递归排序右半部分
        mergeSort(arr, temp, mid + 1, right);

        // 合并两个已排序的部分
        merge(arr, temp, left, mid, right);
    }
}

// 用于调用归并排序的辅助函数
void mergeSort(std::vector<int>& arr) {
    std::vector<int> temp(arr.size());
    mergeSort(arr, temp, 0, arr.size() - 1);
}

int main() {
    std::vector<int> arr = {38, 27, 43, 3, 9, 82, 10};
    mergeSort(arr);
    for (int num : arr) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    return 0;
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值