面试版-八大排序算法

整理一份适合面试的八大排序算法,参考:
https://blog.csdn.net/csdn_baotai/article/details/80293679
https://blog.csdn.net/ysunflower/article/details/80425788
https://blog.csdn.net/prstaxy/article/details/8166360(归并排序)
https://blog.csdn.net/Koala_Tree/article/details/79958965 (十大排序)
特此感谢!

一、算法概述

1.1 算法分类

比较类排序:通过比较来决定元素间的相对次序。

时间复杂度O(nlogn) ~ O(n^2),主要有:冒泡排序,选择排序,插入排序,归并排序,堆排序,快速排序等。
非比较排序,不通过比较来决定元素间的相对次序
时间复杂度可以达到O(n),主要有:计数排序,基数排序,桶排序等。
面试版-八大排序算法_第1张图片

1.2 算法复杂度及稳定性

稳定:如果一个数a原本在b前面,而a = b,排序之后一个a仍然在b的前面。
面试版-八大排序算法_第2张图片

关于不稳定算法可以谐音记忆:快 些/希 选 堆 (美女)~
面试版-八大排序算法_第3张图片

二、代码实现

冒泡排序

冒泡排序算法,是最基本的排序算法, 它属于交换排序。
面试版-八大排序算法_第4张图片

原始代码:

void bubbleSort(int arr[],int len)
{
    for(int i=0;i<len-1;++i)
        for(int j=0;j<len-1-i;++j)
        {
            if(arr[j]>arr[j+1])
                swap(arr[j],arr[j+1]);
        }
}

改进代码:

void bubbleSort1(int arr[],int len)
{
    bool isSwap = true;
    for(int i=0;i<len-1 && isSwap;++i)
    {
        isSwap = false;
        for(int j=0; j<len-1-i;++j)
        {
            if(arr[j]>arr[j+1])
            {
                 isSwap = true;
                 swap(arr[j],arr[j+1]);
            }

        }
    }
}
选择排序
void selectSort(int arr[],int len)
{
    int minIndex;
    for(int i=0;i<len-1;++i)
    {
        minIndex = i;
        for(int j=i+1;j<len;++j)
        {
            if(arr[j]//最小
                minIndex = j;
        }
        if(i != minIndex)
            swap(arr[i],arr[minIndex]);
    }
}
插入排序
void insertSort(int arr[],int len)
{
    int preIndex,current;
    for(int i=1;i1;
        current = arr[i];
        while(preIndex>=0 && arr[preIndex]>current)
        {
            arr[preIndex+1] = arr[preIndex];
            preIndex--;
        }
        arr[preIndex+1] = current; //合适位置
    }

}
归并排序

递归和非递归版本

#include
#include
#include
using namespace std;


void Merge(int arr[], int low, int mid, int high)
{
    //low为第一有序区间的第一个元素,mid为第一有序区间的最后一个元素
    //mid+1为第二有序区间的第一个元素,high为第二有序区间的最后一个元素
    int i = low, j = mid + 1;
    int k = 0; //合并数组索引
    int *temparr = new (nothrow) int[high - low + 1];
    if (!temparr)
    {
        cout << "Error";
        exit(1);
    }
    //顺序选取两个有序区的较小元素,存储到t数组中
    while (i<=mid && j<=high)
    {
        if (arr[i] <= arr[j])
            temparr[k++] = arr[i++];
        else
            temparr[k++] = arr[j++];  
    }

    //若比较完之后,第一个有序区仍有剩余,则直接复制到t数组中
    while (i<=mid)
    {
        temparr[k++] = arr[i++];
    }

    while (j <= high)
    {
        temparr[k++] = arr[j++];
    }

    //将排好序的t存回arr中low到high区间 
    for (i = low, k = 0; i <= high; i++, k++)
        arr[i] = temparr[k];

    delete[] temparr;  //删除指针,由于指向的是数组,必须用delete [] 

}

void MergeSortRecursion(int arr[], int low, int high)
{
    //当low==high时,分治完成,要注意递归结束条件
    if (low < high)
    {
        int mid = (low + high) / 2;
        MergeSortRecursion(arr, low, mid);
        MergeSortRecursion(arr, mid + 1, high);
        Merge(arr, low, mid, high);  //分治后排序
    }
}

//非递归实现 
void MergeSortIteration(int arr[], int n)//参数和递归略不同,n代表数组中元素个数,即数组最大下标是n-1 
{
    //需要考虑第2个序列个数不足的情况
    int size = 1, low, mid, high;
    while (size <= n - 1)
    {
        low = 0;
        while (low + size < n )
        {
            mid = low + size - 1;
            high = mid + size;
            if (high>n - 1)//第二个序列个数不足size 
                high = n - 1;
            Merge(arr, low, mid, high);//调用归并子函数 
            cout << "low:" << low << " mid:" << mid << " high:" << high << endl;//打印出每次归并的区间 
            low = high + 1;//下一次归并时第一关序列的下界 
        }
        size *= 2;//范围扩大一倍 
    }

}
int main() {

    int x[] = { 6,5,3,1,8,7,2,0,4 };
    MergeSortRecursion(x, 0, 8);
    for (int i = 0; i<9; i++)
        cout << x[i] << " ";
    cout << endl;

    int x1[] = { 6,5,3,1,8,7,2,0,4 };
    MergeSortIteration(x1, 9);
    for (int i = 0; i<9; i++)
        cout << x1[i] << " ";

    getchar();
    return 0;
}
快速排序

快速排序是基于Partition的递归,这里参考我之前的文章《基于Partition函数实现快排》

1)从序列中任意选一个元素作为基准,将小于等于基准的元素放到基准前,将大于等于基准的元素放到基准后。
2) 一趟快速排序
3)接下来分别对基准前后两个子序列重复上述步骤。
递归终止条件: begin==end。
最坏情况时,两边极度不均衡(有序序列),此时退化为冒泡排序。
改进法:三者取中法,随机取值等,尽量使两边均衡。
int index = rand() % (end - start + 1) + start; // 随机选择基准
swap(nums[index], nums[end]);

#include
#include
#include
using namespace std;


//Two Pointers思想的分割函数(begin为0,end为n-1)
int Partition(vector<int> &nums, int begin, int end)
{
    int pivot = nums[begin];//第一个记录作为枢轴(也可是在begin和end之间的随机数)
    while (begin < end)
    {
        while (begin < end && nums[end] >= pivot)
        {
            end--;
        }
        nums[begin] = nums[end];//尾部找到小于pivot的值,移到低端

        while (begin < end && nums[begin] <= pivot)
        {
            begin++;
        }
        nums[end] = nums[begin];//头部找到大于pivot的值,移到高端
    }

    nums[begin] = pivot;//枢轴基准归位

    return begin;
}

void QuikSort(vector<int> &nums,int begin, int end)
{
    if (end > begin)
    {
        int index = Partition(nums, begin, end);
        QuikSort(nums, begin, index-1);
        QuikSort(nums, index + 1,end);
    }
}
int main() {

    vector<int>  vec{ 6,5,3,1,8,7,2,0,4 };
    QuikSort(vec, 0, 8); //最大索引
    for (int i = 0; i<9; i++)
        cout << vec[i] << " ";

    getchar();
    return 0;
}
堆排序

参考了:堆排序

void HeapAdjust(vector<int> &nums, int parent, int length)
{
    int root = nums[parent];
    int child = 2 * parent + 1;
    while (child//若有右孩子,并且右孩子值大于左孩子值,则选取右孩子结点
        if (child + 1 < length && nums[child] < nums[child + 1])
            child++;

        if (root >= nums[child])
            break;   //找到合适位置时跳出

        //把较大结点赋值给父节点
        nums[parent] = nums[child];
        //选取孩子大节点更新为父节点(对有影响的分支继续)
        parent = child;
        child = 2 * parent + 1;
    }

    //安插合适位置
    nums[parent] = root;
}

void HeapSort(vector<int> &nums)
{
    int len = nums.size();
    //建堆
    for (int i = len / 2; i >= 0; --i)
        HeapAdjust(nums, i, len);

    for (int i = len - 1; i > 0; --i)
    {
        //最后一个元素和堆顶交换
        swap(nums[0], nums[i]);
        HeapAdjust(nums, 0, i);
    }
}
希尔排序

待完成

你可能感兴趣的:(数据结构与算法)