数据结构-七大排序算法总结

首先总结一下排序的算法分别是什么:

这里写图片描述

冒泡排序

之前写过冒泡排序的思路及其优化,所以再次就不再赘述了。
冒泡排序
在此写一个从后向前冒泡的版本:

//每次冒泡将当前最小值冒到第一个位置上
//[0,bound)表示有序元素
//[bound,size)表示待排序区间
void BubbleSort(int arr[],size_t size)
{
    if(arr==NULL||size<=0)
    {
        return;
    }
    size_t bound=0;
    for(;bound<size;bound++)
    {
        size_t cur=size-1;
        for(;cur>0;cur--)
        {
            if(arr[cur]<arr[cur-1])
            {
                Swap(&arr[cur],&arr[cur-1]);
            }
        }
    }
}

总结:
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定排序

选择排序

选择排序的思路主要是采取了一种“打擂台”的思想。
一个擂主,多个挑战者挑战,不符合排序要求的交换。
下面一张图解释一下大致过程:
这里写图片描述
这里写图片描述

排序过程大致过程如上所述。
下面是代码:

//[0,bound)表示有序元素
//[bound,size)表示待排序区间
void SelectSort(int arr[],size_t size)
{
    if(arr==NULL||size<=0)
    {
        return;
    }
    size_t bound=0;
    for(;bound<size;bound++)
    {
        size_t cur=bound+1;
        for(;cur<size;cur++)
        {
            if(arr[bound]>arr[cur])//bound是擂主
            {
                Swap(&arr[bound],&arr[cur]);
            }
        }
    }
}

总结:
时间复杂度:O(n^2)
空间复杂度:O(1)
不稳定排序

插入排序

插入排序的思路将不再是交换值,而是搬运。
步骤大致如下:
1.定义好边界,仍然以[0,bound)表示已排序区间,[bound,size)表示未排序区间。
2.保存bound指向的元素,以防止搬运时被覆盖,数据丢失。
3.从后往前的遍历,找到一个合适的位置存放刚刚保存下来的值。
也就是说,将bound指向的值,插入已排序好的线性表中。
采用一边找,一边搬运的方法,更节省时间。

下面用图来解释一下大致排序过程
这里写图片描述

下面是代码:

void InsertSort(int arr[],size_t size)
{
    if(arr==NULL||size<=0)
    {
        return;
    }
    size_t bound=1;
    for(;bound<size;bound++)
    {
        size_t cur=bound;
        int bound_value=arr[bound];
        for(;cur>0;cur--)
        {
            if(bound_value<arr[cur-1])
            {
                //进行搬运
                arr[cur]=arr[cur-1];
            }
            else
            {
                //说明此时已经找到了合适的位置
                break;
            }
        }
        //填坑,将bound_value的值填入
        arr[cur]=bound_value;
    }
}

特点:
1.当数组元素个数较少时,执行效率比较快。
2.如果数组基本有序,执行效率比较快。

总结:
时间复杂度:O(N^2)
空间复杂度:o(1)

希尔排序

希尔排序是插入排序的一种,又称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。本质上是一种分组插入的方法。
希尔排序具有以下两个特点:
(1)插入排序在对几乎已经排好序的数据操作时,效率高;
(2)插入排序在对元素个数较少的数据操作时,效率高。
举例说明:

这里写图片描述

void ShellSort(int arr[],size_t size)
{
    if(arr==NULL||size<=0)
    {
        return;
    }
    size_t gap=size/2;
    for(;gap>0;gap/=2)//生成步长序列
    {
        size_t bound=gap;
        for(;bound<size;bound++)//进行插入排序
        {
            int bound_value=arr[bound];
            size_t cur=bound;
            for(;cur>=gap;cur-=gap)//线性表元素的搬运
            {
                if(arr[cur-gap]>bound_value)
                {
                    arr[cur]=arr[cur-gap];
                }
                else
                {
                    break;
                }
            }
            arr[cur]=bound_value;
        }
    }

}

总结:
时间复杂度:O(N^2)
空间复杂度:O(1)
不稳定排序

归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and
Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

这里写图片描述

递归实现:

void _MergeArray(int arr[],int beg,int mid,int end,int* tmp)
{
    int cur1=beg;
    int cur2=mid;
    int tmp_index=beg;
    while(cur1<mid&&cur2<end)
    {
        if(arr[cur1]<arr[cur2])
        {
            tmp[tmp_index++]=arr[cur1++];
        }
        else
        {
            tmp[tmp_index++]=arr[cur2++];
        }
    }
    while(cur1<mid)
    {
        tmp[tmp_index++]=arr[cur1++];
    }
    while(cur2<end)
    {
        tmp[tmp_index++]=arr[cur2++];
    }
    memcpy(arr+beg,tmp+beg,sizeof(int)*(end-beg));
}
void _MergeSort(int arr[],int beg,int end,int *tmp)
{
    if(end-beg<=1)
    {
        return;
    }
    int mid=beg+(end-beg)/2;
    _MergeSort(arr,beg,mid,tmp);
    _MergeSort(arr,mid,end,tmp);
    _MergeArray(arr,beg,mid,end,tmp);

}
void MergeSort(int arr[],size_t size)
{
    if(arr==NULL||size<=0)
    {
        return;
    }
    int *tmp=(int*)malloc(sizeof(int)*size);
    _MergeSort(arr,0,size,tmp);
}

非递归实现:

void MergeSortByLoop(int arr[],size_t size)
{
    if(arr==NULL||size<=0)
    {
        return;
    }
    int *tmp=(int*)malloc(sizeof(int)*size);
    size_t gap=1;
    for(;gap<size;gap*=2)
    {
        size_t i=0;
        for(;i<size;i+=2*gap)
        {
            size_t beg=i;
            size_t mid=i+gap;
            size_t end=i+2*gap;
            if(mid>size)
            {
                mid=size;
            }
            if(end>size)
            {
                end=size;
            }
            _MergeArray(arr,beg,mid,end,tmp);
        }
    }
}

总结:
时间复杂度:O(N*logN)
空间复杂度:O(N)
稳定排序

快速排序

快速排序可以说是这七种排序算法中最重要的一个,面试几率最高的一个排序算法。原因是快速排序足够快。
过程如下:
这里写图片描述

/////////方法1.交换法////////////
int Partion(int arr[],size_t beg,size_t end)
{   
    //1)定义好区间的边界
    //2)取最后一个元素作为基准值
    //3)从左到右找到第一个大于基准值的元素
    //4)从右往左找到第一个小于基准值的元素
    //5)交换(left<right)
    int left=beg;
    int right=end-1;
    int key=arr[right];
    while(left<right)
    {
        while(left<right&&arr[left]<=key)
        {
            left++;    
        }
        while(left<right&&arr[right]>=key)
        {
            right--;
        }
        if(left<right)
        {
            Swap(&arr[left],&arr[right]);
        }
    }
    //此时将left指向的值和最后一个元素进行交换
    //此时left指向的值,一定大于基准值
    Swap(&arr[left],&arr[end]-1);
    return left;
}

/////////方法2.挖坑法////////////

int Partion2(int arr[],size_t beg,size_t end)
{
    //定义好区间边界
    int left=beg;
    int right=end-1;
    //取最后一个元素做基准值
    int key=arr[right]; 
    while(left<right)
    {
        while(left<right&&arr[left]<=key)
        {
            ++left;
        }
        if(left<right)
        {
            arr[right--]=arr[left];
        }
        while(left<right&&arr[right]>=key)
        {
            --right;
        }
        if(left<right)
        {
            arr[left++]=arr[right];
        }
    }
    arr[left]=key;
    return left;
}
void _QuickSort(int arr[],size_t beg,size_t end)
{   
    if(end-beg<=1)
    {
        return;
    }
    size_t mid=Partion(arr,beg,end);
    _QuickSort(arr,beg,mid);
    _QuickSort(arr,mid+1,end);

}
void QuickSort(int arr[],size_t size)
{
    if(arr==NULL||size<=0)
    {
        return;
    }
    _QuickSort(arr,0,size);
}


挖坑法与交换法本质上是没有区别的,挖坑法实际上是将交换的动作拆解开了。
非递归版本实现:借助一个栈。通过入栈取栈顶元素出栈的过程,取出当前需要划分的区间。

void QuickSortByLoop(int arr[],size_t size)
{
    if(arr==NULL||size<=0)
    {
        return;
    }
    SeqStack stack;
    SeqStackInit(&stack);
    int beg=0;
    int end=size;
    SeqStackPush(&stack,beg);
    SeqStackPush(&stack,end);
    while(stack.size>0)
    {
        SeqStackTop(&stack,&end);
        SeqStackPop(&stack);
        SeqStackTop(&stack,&beg);
        SeqStackPop(&stack);
        if(end-beg<=1)
        {
            continue;
        }
        int mid=Partion(arr,beg,end);
        SeqStackPush(&stack,beg);
        SeqStackPush(&stack,mid);
        SeqStackPush(&stack,mid+1);
        SeqStackPush(&stack,end);
    }
}

对快速排序的改进方法:

  1. 三值取中确定基准值
  2. 当区间比较小时,使用插入排序直接对区间进行排序,从而有效地减少递归次数
  3. 递归达到一定程度之后,使用堆排序对剩余待排序区间进行排序即可

总结:
时间复杂度:
最坏:O(n^2)
平均:O(n*logn)
空间复杂度:O(logn)

猜你喜欢

转载自blog.csdn.net/mxrrr_sunshine/article/details/80481482