常用的排序算法

面试常用到的排序算法



一、冒泡排序

1.思路:通过对待排序序列从前向后(从下标较小的元素开始),依次对相邻两个元素的值进行两两比较,若发现前一个数大于后一个数则交换,使值较大的元素逐渐从前移向后部,就如果水底下的气泡一样逐渐向上冒。
2.具体过程注意事项
(1)总计需要进行(n-1)轮排序,也就是(n-1)次大循环
(2)每轮排序比较的次数逐轮减少
(3)如果发现在某趟排序中,没有发生一次交换, 可以提前结束冒泡排序
(4)时间复杂度是O(N^2) 在有序的时候,很快,因为有exchange变量优化了代码,在乱序的时候很慢很慢。
3.代码实现

#include <iostream>
#include <vector>

using namespace std;

void Swap(int &a, int &b)
{
    int temp = a;
    a = b;
    b = temp;
}

void BubbleSort(vector<int>& nums)
{
    int end = nums.size() - 1;
    while (end)
    {
        int exchange = 0;
        for (int i = 0; i < end; i++)
        {
            if (nums[i] > nums[i + 1])
            {
                swap(nums[i], nums[i + 1]);
                exchange++;
            }
        }
        
        end--;
        if (exchange == 0) break;
    }
}

二、插入排序

1.思路:在待排序的元素中,假设前n-1个元素已有序,现将第n个元素插入到前面已经排好的序列中,使得前n个元素有序。按照此法对所有元素进行插入,直到整个序列有序。
  但我们并不能确定待排元素中究竟哪一部分是有序的,所以我们一开始只能认为第一个元素是有序的,依次将其后面的元素插入到这个有序序列中来,直到整个序列有序为止。
2.具体过程注意事项
(1)需要两层循环,第一层循环判断是否前i个元素有序,第二层将非有序元素修正成有序的;
(2)最后找到正确位置 j + 1 之后需要将nums[i]放在这个位置;
3.代码实现

#include <iostream>
#include <vector>

using namespace std;

void InsertSort(vector<int>& nums)
{
    int n = nums.size();
    // 外层循环查看是否有序
    for (int i = 1; i < n; i++)
    {
        if (nums[i] < nums[i - 1])
        {
            int temp = nums[i];
            // 内层循环找到该元素需要放到什么位置
            int j = 0;
            for (j = i - 1; j >= 0 && nums[j] > temp; j--)
            {
                nums[j + 1] = nums[j];
            }
            // j + 1的位置是在i应该呆的位置
            nums[j + 1] = temp;
        }
    }
}

三、希尔排序

1.思路:插入排序的优化版,相当于把直接插入排序中的1换成gap而已;先选定一个小于N的整数gap作为第一增量,然后将所有距离为gap的元素分在同一组,并对每一组的元素进行直接插入排序。然后再gap–,重复上述操作。
2.当gap==1时就是直接插入排序,就相当于整个序列被分到一组,进行一次直接插入排序,排序完成。
3.代码实现

#include <iostream>
#include <vector>

using namespace std;

void Swap(int &a, int &b)
{
    int temp = a;
    a = b;
    b = temp;
}

void ShellSort(vector<int>& nums)
{
    int n = nums.size();
    int gap = n;
    while (gap > 1)
    {
        gap = gap / 3 + 1;
        for (int i = gap; i < n; i++)
        {
            if (nums[i] < nums[i - gap])
            {
                int tmp = nums[i];
                int j = 0;
                for (j = i - gap; j >= 0 && tmp < nums[j]; j -= gap)
                {
                    swap(nums[j + gap], nums[j]);
                }
                nums[j + gap] = tmp;
            }
        }
    }
}

四、选择排序

1.思路:在待排序的元素中,找到未排序的元素中最小的那个放到已经排好序的数组后,即第i次循环就放到nums[i];
2.具体过程注意事项
(1)需要两层循环,第一层循环表示经过的循环数,第二层循环找到未排序数组中最小的数;
3.代码实现

#include <iostream>
#include <vector>

using namespace std;

void Swap(int &a, int &b)
{
    int temp = a;
    a = b;
    b = temp;
}

void SelectSort(vector<int>& nums)
{
    int n = nums.size();
    for (int i = 0; i < n - 1; i++)
    {
        int min = i;
        for (int j = i + 1; j < n; j++)
        {
            if (nums[min] > nums[j]) min = j;
        }
        swap(nums[i], nums[min]);
    }
}

五、快速排序

1.思路:快排是分治算法的一种应用,分治算法都有三步:①分成子问题;②递归处理子问题;③子问题合并;
通过一趟排序将待记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个有序的目的。
2.代码实现

#include <iostream>
#include <vector>

using namespace std;

void Swap(int &a, int &b)
{
    int temp = a;
    a = b;
    b = temp;
}

void QuickSort(vector<int>& nums, int l, int r)
{
    if (l >= r) return;
    int i = l - 1, j = r + 1;
    int x = nums[l + r >> 1];
    while (i < j)
    {
        do i++; while (nums[i] < x);
        do j--; while (nums[j] > x);
        if (i < j) swap(nums[i], nums[j]);
    }
    
    QuickSort(nums, l, j);
    QuickSort(nums, j + 1, r);
}

六、归并排序

1.思路:
①有数组 q, 左端点 l, 右端点 r;
②确定划分边界 mid;
③递归处理子问题 q[l…mid], q[mid+1…r]
④合并子问题:
<1>主体合并:至少有一个小数组添加到 tmp 数组中;
<2>收尾:可能存在的剩下的一个小数组的尾部直接添加到 tmp 数组中;
<3>复制回来:tmp 数组覆盖原数组;
2.代码实现

#include <iostream>
#include <vector>

using namespace std;


void MergeSort(vector<int>& nums, int l, int r)
{
    if (l >= r) return;
    
    int mid = l + r + 1 >> 1;
    
    MergeSort(nums, l, mid - 1);
    MergeSort(nums, mid, r);
    
    int k = 0, i = l, j = mid;
    vector<int> tmp(r - l + 1, 0);
    
    while (i < mid && j <= r)
    {
        if (nums[i] < nums[j]) tmp[k++] = nums[i++];
        else tmp[k++] = nums[j++];
    }
    while(i < mid) tmp[k++] = nums[i++];
    while(j <= r) tmp[k++] = nums[j++];
    
    for (int k = 0, i = l; i <= r; i++, k++) nums[i] = tmp[k];
}

七、堆排序

1.什么是堆?
大堆:父亲大于儿子 小堆:父亲小于儿子(父亲,儿子是二叉树的概念)
堆的物理结构:数组 逻辑结构:完全二叉树;
2.思路:
①把无序数组构建成二叉堆;
②循环删除堆顶元素,移到集合尾部,调节堆产生新的堆顶;
3.堆排序是不稳定的排序,空间复杂度为O(1),平均的时间复杂度为O(nlogn),最坏情况下也稳定在O(nlogn)
4.代码实现

#include <iostream>
#include <vector>

using namespace std;

// 向下调整, 构建大根堆
void HeapJust(vector<int>& nums, int start, int end)
{
    int temp = nums[start];
    for (int i = start * 2 + 1; i <= end; i = i * 2 + 1)
    {
        // 找到左右节点中最大的节点
        if (i < end && nums[i] < nums[i + 1]) i++;
        // start用于记录当前左右节点的根节点
        if (temp < nums[i])
        {
            nums[start] = nums[i];
            start = i;
        }
        else break;
    }
    nums[start] = temp;
}

void HeapSort(vector<int>& nums, int len)
{
    // 从堆的最后一个非叶子节点到根节点,开始构造大根堆
    for (int i = len / 2 - 1; i >= 0; i--) HeapJust(nums, i, len - 1);
    // 开始排序,每次将根节点与未排序的最后一个节点交换
    int temp = 0;
    for (int i = 0; i < len - 1; i++)
    {
        temp = nums[0];
        nums[0] = nums[len - 1 - i];
        nums[len - 1 - i] = temp;
        HeapJust(nums, 0, len - 1 - i - 1);
    }
}

八、基数排序

1.思路:是将整数按位数切割成不同的数字,然后按每个位数分别比较;
基数排序的原理


总结

以上我对常见的几种排序做了一个总结,并且把代码全部手动实现了一边,没有放main函数,方便之后每次面试之前复习;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值