0
点赞
收藏
分享

微信扫一扫

大厂开发面试八股文:吊打面试官——排序算法

Hyggelook 2022-04-05 阅读 25
后端c++

文章目录


在这里插入图片描述
在这里插入图片描述

稳定:冒泡,插入,归并

交换排序

冒泡排序

依次比较相邻两元素,若前一元素大于后一元素则交换之,直至最后一个元素即为最大;然后重新从首元素开始重复同样的操作,直至倒数第二个元素即为次大元素;依次类推。如同水中的气泡,依次将最大或最小元素气泡浮出水面。

#include <iostream>
#include <algorithm>
#include<vector>

using namespace std;

void bubblesort(vector<int> &v)
{
    //若flag为true说明有过数据交换,否则停止循环
    bool flag=true;
    for(int i=0;i<v.size() && flag;i++)
    {
        flag=false;
        for(int j=v.size()-2;j>=i;j--)
        {
            if(v[j]>v[j+1])
            {
                swap(v[j],v[j+1]);
                flag=true;
            }
        }

    }
}

int main() {
    vector<int> v{3,44,35,5,47,15,36,26,27,2,46,4,19,50,48};

    bubblesort(v);

    for(auto x:v)
        cout<<x<<' ';
    return 0;
}

在这里插入图片描述

快速排序

快速排序的思想:任取一个数据元素作为基准,做一次划分,讲所有数据元素集合分成3部分;中间部分值包含一个基准元素,它的前面包含所有关键字小于基准的数据元素,它的后面包含所有关键字大于基础的数据元素,然后分别对前后两部分进行划分。

对数据元素 v 1   v n v_1~v_n v1 vn做一趟划分,不失一般性,选择第一个元素作为基准元素,用t代表基准。设i=1,j=n,划分过程如下:

  • 1.比较基准元素与第j个元素 a j a_j aj,如果 a j a_j aj>t,则j- -;继续步骤1;如果 a j < t a_j<t aj<t,则将 a j a_j aj移到i的位置,i=i+1转步骤2。
  • 2.比较基准元素与第i个元素 a i a_i ai,如果 a j a_j aj<t,则i++;继续步骤2;如果 a i > t a_i>t ai>t,则将 a i a_i ai移到j的位置,j=j+1转步骤3。
  • 3.重复步骤1和2,直到i=j,将基准元素放到i的位置。

快排优化:

  • 3 种快排基准选择方法:

随机(rand函数)、固定(队首、队尾)、三数取中(队首、队中和队尾的中间数)

  • 4种优化方式:

优化1:当待排序序列的长度分割到一定大小后,使用插入排序

优化2:在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割

优化3:优化递归操作

优化4:使用并行或多线程处理子序列

#include <iostream>
#include <algorithm>
#include<vector>

using namespace std;


    int Partition(vector<int> &v,int low ,int high)
    {
        int pivot=v[low];//用子表的第一个记录作枢轴记录
        while(low<high)
       {
            while(low<high && v[high]>=pivot)
                high--;//找出第一个比pivot小
            v[low]=v[high];//high指示的值小于pivot,交换
            while(low<high && v[low]<=pivot)
                low++;//找出第一个比pivot大
            v[high]=v[low];//low指示的值大于pivot,交换
       }
       
       v[low]=pivot;
       return low;
    }

void quickSort(vector<int>&v,int low,int high)
{
    if(low<high)
    {
        int i=Partition(v, low, high);
        quickSort(v, low, i-1);
        quickSort(v, i+1, high);
    }
}



int main() {
    vector<int> v{3,44,35,5,47,15,36,26,27,2,46,4,19,50,48};
    int length=v.size();

    quickSort(v,0,length);

    for(auto x:v)
        cout<<x<<' ';
    return 0;

}

在这里插入图片描述

选择排序

简单选择排序

#include <iostream>
#include<algorithm>
#include<vector>

using namespace std;

void SelectSort(vector<int> &v)
{
    int min_index;
    for(int i=0;i<v.size();i++)
    {
        min_index=i;
        for(int j=i+1;j<v.size();j++)
        {
            if(v[min_index]>v[j])//如果有小于当前最小值的关键字
                min_index=j;//将此关键字的下标赋值给min_index
        }
        if(min_index!=i)
            swap(v[min_index],v[i]);

    }
}

int main() {
    vector<int> v={3,44,35,5,47,15,36,26,27,2,46,4,19,50,48};
    SelectSort(v);

    for(auto x:v)
        cout<<x<<' ';
    return 0;
}

在这里插入图片描述

堆排序

堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆

再简单总结下堆排序的基本思路:

a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

#include <stdio.h>

void display(int array[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");
}

void swap(int array[], int x, int y) {
    int key  = array[x];
    array[x] = array[y];
    array[y] = key;
}

// 从大到小排序
// void Down(int array[], int i, int n) {
//     int child = 2 * i + 1;
//     int key   = array[i];
//     while (child < n) {
//         if (child + 1 < n && array[child] > array[child + 1]) {
//             child++;
//         }
//         if (key > array[child]) {
//             swap(array, i, child);
//             i = child;
//         } else {
//             break;
//         }
//         child = child * 2 + 1;
//     }
// }

// 从小到大排序
void Down(int array[], int i, int n) { // 最后结果就是大顶堆
    int parent = i;                    // 父节点下标
    int child  = 2 * i + 1;            // 子节点下标
    while (child < n) {
        if (child + 1 < n && array[child] < array[child + 1]) { // 判断子节点那个大,大的与父节点比较
            child++;
        }
        if (array[parent] < array[child]) { // 判断父节点是否小于子节点
            swap(array, parent, child);     // 交换父节点和子节点
            parent = child;                 // 子节点下标 赋给 父节点下标
        }
        child = child * 2 + 1; // 换行,比较下面的父节点和子节点
    }
}

void BuildHeap(int array[], int size) {
    for (int i = size / 2 - 1; i >= 0; i--) { // 倒数第二排开始, 创建大顶堆,必须从下往上比较
        Down(array, i, size);                 // 否则有的不符合大顶堆定义
    }
}

void HeapSort(int array[], int size) {
    printf("初始化数组:");
    BuildHeap(array, size); // 初始化堆
    display(array, size);   // 查看初始化结果
    for (int i = size - 1; i > 0; i--) {
        swap(array, 0, i); // 交换顶点和第 i 个数据
                           // 因为只有array[0]改变,其它都符合大顶堆的定义,所以可以从上往下重新建立
        Down(array, 0, i); // 重新建立大顶堆

        printf("排序的数组:");
        display(array, size);
    }
}

int main() {
    int array[] = {49, 38, 65, 97, 76, 13, 27, 49, 10};
    int size    = sizeof(array) / sizeof(int);

    // 打印数据
    printf("%d \n", size);
    printf("排序前数组:");
    display(array, size);
    HeapSort(array, size);

    return 0;
}

#include<iostream>
#include<vector>
using namespace std;
void swap(vector<int>& arr, int a,int b){
    arr[a]=arr[a]^arr[b];
    arr[b]=arr[a]^arr[b];
    arr[a]=arr[a]^arr[b];
}
void adjust(vector<int>& arr,int len,int index){
    int maxid=index;
    // 计算左右子节点的下标   left=2*i+1  right=2*i+2  parent=(i-1)/2
    int left=2*index+1,right=2*index+2;

    // 寻找当前以index为根的子树中最大/最小的元素的下标
    if(left<len and arr[left]<arr[maxid]) maxid=left;
    if(right<len and arr[right]<arr[maxid]) maxid=right;

    // 进行交换,记得要递归进行adjust,传入的index是maxid
    if(maxid!=index){
        swap(arr,maxid,index);
        adjust(arr,len,maxid);
    }
}
void heapsort(vector<int>&arr,int len){
    // 初次构建堆,i要从最后一个非叶子节点开始,所以是(len-1-1)/2,0这个位置要加等号
    for(int i=(len-1-1)/2;i>=0;i--){
        adjust(arr,len,i);
    }

    // 从最后一个元素的下标开始往前遍历,每次将堆顶元素交换至当前位置,并且缩小长度(i为长度),从0处开始adjust
    for(int i=len-1;i>0;i--){
        swap(arr,0,i);
        adjust(arr,i,0);// 注意每次adjust是从根往下调整,所以这里index是0!
    }
}
int main(){
    vector<int> arr={3,4,2,1,5,8,7,6};

    cout<<"before: "<<endl;
    for(int item:arr) cout<<item<<" ";
    cout<<endl;

    heapsort(arr,arr.size());

    cout<<"after: "<<endl;
    for(int item:arr)cout<<item<<" ";
    cout<<endl;

    return 0;
}

插入排序

直接插入排序

数列前面部分看为有序,依次将后面的无序数列元素插入到前面的有序数列中,初始状态有序数列仅有一个元素,即首元素。在将无序数列元素插入有序数列的过程中,采用了逆序遍历有序数列,相较于顺序遍历会稍显繁琐,但当数列本身已近排序状态效率会更高。

#include <iostream>
#include <algorithm>
#include<vector>

using namespace std;

void InsertSort(vector<int> &v)
{
    int i,j,temp;
    for(i=1;i<v.size();i++)
    {
        temp=v[i];
        for(j=i-1;j>=0 && temp<v[j];j--)
            v[j+1]=v[j];//后移一位

        v[j+1]=temp;
    }
}

int main() {
    vector<int> v{3,44,35,5,47,15,36,26,27,2,46,4,19,50,48};

    InsertSort(v);

    for(auto x:v)
        cout<<x<<' ';
    return 0;
}

在这里插入图片描述

希尔排序

希尔排序的基本思想如下:选定一个增量 h 1 ( h 1 < n ) h_1 (h_1<n) h1(h1<n),把序列中的数据元素从第1个开始分组,所有相距 h 1 h_1 h1的数据元素分为一组,一共有 h 1 h_1 h1组, 并在各组内采用直接插入提序,使各组内的数据元素排好序,然后选第个增量 h 2 ( h 2 < h 1 ) h_2(h_2<h_1) h2(h2<h1),以 h 2 h_2 h2为距离对数据元素进行分组及排序;重复该分组及排序过程,知道增量为1。
希尔排序的性能取决于增量序列的选择。希尔提出取 h 2 = n / 2 , h i − 1 = [ h i 2 ] h_2=n/2,h_{i-1}=[\frac{h_i}{2}] h2=n/2,hi1=[2hi]。Knuth提出取 h 2 = n / 2 , h i − 1 = [ h i 3 ] h_2=n/2,h_{i-1}=[\frac{h_i}{3}] h2=n/2,hi1=[3hi]

#include <iostream>
#include <algorithm>
#include<vector>

using namespace std;

void ShellSort(vector<int> &v)
{
    int increment=v.size()/2;
    while(increment)
    {
        for(int i=increment;i<v.size();i+=1)//对各子表做直接插入排序
        {
            int temp=v[i], j;
            for(j=i-increment;j>=0 && temp<v[j];j-=increment)
            {
                v[j+increment]=v[j];
            }
            v[j+increment]=temp;
        }
        increment/=2;
    }
}

int main() {
    vector<int> v{3,44,35,5,47,15,36,26,27,2,46,4,19,50,48};

    ShellSort(v);

    for(auto x:v)
        cout<<x<<' ';
    return 0;

}

在这里插入图片描述

归并排序

  • 归并排序 ( Merging Sort) 就是利用归并的思想实现的排序方法。原理是假设初始序列含有 n 个记录 , 则可以看成是 n 个有序的子序列,每个子序列的长度为1 ,然后两两归并,得到[[n/2] ( [x]表示不小于 x 的最小整数)个长度为 2或1的有序子序列;再两两归并 ,…,如此重复 , 直至得到 一个长度为 n 的有序序列为止 ,这种排序方法称为2路归并排序 。
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

void mergeSort(vector<int> &q, int l, int r){
    if(l >= r)
        return;
    int mid = l + r >> 1;
    mergeSort(q, l, mid);
    mergeSort(q, mid + 1, r);
    static vector<int> w;
    w.clear();
    int i = l, j = mid + 1;
    while(i <= mid && j <= r){
        if(q[i] > q[j])
            w.push_back(q[j++]);
        else
            w.push_back(q[i++]);
    }
    while(i <= mid)
        w.push_back(q[i++]);
    while(j <= mid)
        w.push_back(q[j++]);
    for(int i : w)
        q[l++] = i;
}

int main(){
    vector<int> v{3,44,35,5,47,15,36,26,27,2,46,4,19,50,48};
    int n=v.size();
    mergeSort(v, 0, n - 1);
    for(auto x : v)
        cout << x << ' ';
    return 0;
}

在这里插入图片描述

基数排序

#include <iostream>
#include <vector>

using namespace std;

int get(int x, int i){
    while(i--)
        x /= 10;
    return x % 10;
}

void radixSort(vector<int> &q, int n){
    vector<vector<int>> cnt(10);
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 10; j++)
            cnt[j].clear();
        for(int j = 0; j < n; j++)
            cnt[get(q[j], i)].push_back(q[j]);
        for(int j = 0, k = 0; j < 10; j++){
            for(int x : cnt[j])
                q[k++] = x;
        }
    }
}

int main(){
    vector<int> v{3,44,35,5,47,15,36,26,27,2,46,4,19,50,48};
    int n=v.size();
    radixSort(v, n);

    for(auto x : v)
        cout << x << ' ';

    return 0;
}

在这里插入图片描述

举报

相关推荐

0 条评论