八大排序(下)

2022-11-10 15:04:56 浏览数 (1)

一、快速排序

1.挖坑法

1.过程

1.为了使用方便我们默认第一个数为key,key的值可以看作单独提出来了,key所在的(pivot)坑的位置 先从右边开始找比key小的数,找到后将值传给pivot所在位置,同时pivot指向右边

2.再从左边找比key大的数,找到后将值传给左边pivot所在位置 ,同时pivot指向左边

3.当begin与end指向同一个位置时,则将关键字传入进去 ,循环结束

2.对于递归结束条件的分析

当递归到最后时,发现只剩下一个数或者没有数存在, 而在循环中成立的条件为 lelft<right 当left==right时,只有一个数 当left>right时,没有数存在

3. 代码实现

代码语言:javascript复制
void swap(int* pa, int* pb)
{
    int tmp = 0;
    tmp = *pa;
    *pa = *pb;
    *pb = tmp;
}
int getmid(int* a, int left, int right)//三数取中  为了防止有序时时间复杂度为O(N^2)
{
    int mid = (left   right) / 2;
    if (a[left] > a[mid])
    {
        if (a[right] > a[left])
        {
            return left;
        }
        else if (a[mid] > a[right])
        {
            return mid;
        }
        else
        {
            return right;
        }
    }
    else//a[left]  <a[mid]
    {
        if (a[right] < a[left])
        {
            return left;
        }
        else if (a[mid] < a[right])
        {
            return mid;
        }
        else
        {
            return right;
        }
    }
}
void   quicksort(int*a,int  left, int right )
{
    if (left >= right)//left==right时,只存在一个值,left>right时,区间不存在
    {
        return;
    }
    int index = getmid(a, left, right);
    swap(&amp;a[left], &amp;a[index]);//默认key的位置为左边第一个
    int begin = left;
    int end =  right;
    int pivot = begin;//pivot代表坑位
    int key = a[begin];
    while (begin < end)//一趟排序
    {
        while (a[end] >= key&amp;&amp;begin<end)//右边找小,放到左边
        {
            end--;
        }
        a[pivot] = a[end];//把小的放在左边的坑位中,自己形成新的坑位
        pivot = end;

        while (a[begin] <= key&amp;&amp;begin<end)//左边找大,放到右边
        {
            begin  ;
        }
        a[pivot] = a[begin];//把大的放在右边的坑位中,自己形成新的坑位
        pivot = begin;
    }
    pivot = begin;//当begin==end时,循环结束,将关键字赋值给begin所在位置
    a[pivot] = key;//通过坑位分成两部分   [left    pivot-1] pivot [pivot 1   right]
    if (pivot - 1 - left > 10) //小区间优化
    {
        quicksort(a, left, pivot - 1);
    }
    else
    {
        insertsort(a   left, pivot - 1 - left 1);//随着递归的变化 ,左区间有可能不从0开始,右区间是下标变化要 1
    }
    if (right - (pivot   1) > 10)
    {
        quicksort(a, pivot   1, right);
    }
    else
    {
        insertsort(a   pivot 1, right - (pivot   1) 1);
    }
    
    
}

4.快速排序的时间复杂度

理想情况下,每次都用坑pivot 将left与right区间平分 即满二叉树

每一层都会将所有数遍历一遍,所有每一层的时间复杂度为O(N) 一共遍历了高度次 根据二叉树性质:2^h-1=N h=log N 快速排序的整体时间复杂度为O(N*logN)

5.三数取中

快排什么时候为最坏情况 有序时最坏 以顺序为列 ,每次只能选出一个数 此时的时间复杂度为O(N^2)

所以为了防止这种情况的发生,采用三数取中

6.小区间优化

使用小区间优化是为了减少函数调用的次数

当我们递归会发现最后几层的函数调用占据了绝大多数 我们假设当一个区间内相差不超过10,就消除 消除的部分则使用直接插入排序 直接插入排序在这里:八大排序(上)

2.左右指针法

1.过程

简单来说,就是先从右面找比关键字小的 ,再从左边找比关键字大的 ,两者交换, 当left==right时,将此时的值与关键字交换

2.代码实现

代码语言:javascript复制
void swap(int* pa, int* pb)
{
    int tmp = 0;
    tmp = *pa;
    *pa = *pb;
    *pb = tmp;
}
int  partsort(int* a, int left, int right)//左右指针法
{
    if (left >= right)
    {
        return;
    }
    int begin = left;
    int end = right;
    int key = left;
    while (begin < end)
    {
        while (a[end] >= a[key] &amp;&amp; begin < end)
        {
            end--;
        }
        while (a[begin] <= a[key] &amp;&amp; begin < end)
        {
            begin  ;
        }
        swap(&amp;a[begin], &amp;a[end]);//当左边找到小的 ,右边找到大的时候 就将两者值交换
    }
    swap(&amp;a[key], &amp;a[begin]);
    return begin;    // [left ,begin-1] begin [begin 1,  right]
}


int getmid(int* a, int left, int right)//三数取中  为了防止有序时时间复杂度为O(N^2)
{
    int mid = (left   right) / 2;
    if (a[left] > a[mid])
    {
        if (a[right] > a[left])
        {
            return left;
        }
        else if (a[mid] > a[right])
        {
            return mid;
        }
        else
        {
            return right;
        }
    }
    else//a[left]  <a[mid]
    {
        if (a[right] < a[left])
        {
            return left;
        }
        else if (a[mid] < a[right])
        {
            return mid;
        }
        else
        {
            return right;
        }
    }
}
void   quicksort(int* a, int  left, int right)
{
    int keyindex = partsort(a,left, right);
    if (keyindex - 1 - left > 10)
    {
        quicksort(a, left, keyindex - 1);
    }
    else
    {
        insertsort(a   left, keyindex - 1 - left   1);//随着递归的变化 ,左区间有可能不从0开始,右区间是下标变化要 1
    }
    if (right - (keyindex   1) > 10)
    {
        quicksort(a, keyindex   1, right);
    }
    else
    {
        insertsort(a   keyindex   1, right - (keyindex   1)   1);
    }


}

3.前后指针法

1.过程

1.当cur的值比key大时,cur

2.当cur的值比key小时 ,prev ,并交换两者的值

3.当cur遍历完数组时,就将prev位置的值与key位置的值交换

2.代码实现

代码语言:javascript复制
void swap(int* pa, int* pb)
{
    int tmp = 0;
    tmp = *pa;
    *pa = *pb;
    *pb = tmp;
}
int getmid(int* a, int left, int right)//三数取中  为了防止有序时时间复杂度为O(N^2)
{
    int mid = (left   right) / 2;
    if (a[left] > a[mid])
    {
        if (a[right] > a[left])
        {
            return left;
        }
        else if (a[mid] > a[right])
        {
            return mid;
        }
        else
        {
            return right;
        }
    }
    else//a[left]  <a[mid]
    {
        if (a[right] < a[left])
        {
            return left;
        }
        else if (a[mid] < a[right])
        {
            return mid;
        }
        else
        {
            return right;
        }
    }
}
int partsort(int* a, int left, int right)//前后指针法
{
    if (left >= right)
    {
        return 0;
    }
    int key = left;
    int prev = left;
    int cur = left   1;
    while (cur <= right)
    {
        if (a[cur] < a[key] &amp;&amp;   prev != cur)//如果cur位置值比key位置值小 ,prev   并交换两者值
        {                                //如果prev向后移后的值与key位置值相等就不进入循环 没有交换的必要
            prev  ;
            swap(&amp;a[prev], &amp;a[cur]);
        }
        cur  ;

    }
    swap(&amp;a[prev], &amp;a[key]);//当cur出了right范围,将prev位置值与key位置值交换
    return prev;
}
void   quicksort(int* a, int  left, int right)
{
    int keyindex = partsort(a,left, right);
    if (keyindex - 1 - left > 10)
    {
        quicksort(a, left, keyindex - 1);
    }
    else
    {
        insertsort(a   left, keyindex - 1 - left   1);//随着递归的变化 ,左区间有可能不从0开始,右区间是下标变化要 1
    }
    if (right - (keyindex   1) > 10)
    {
        quicksort(a, keyindex   1, right);
    }
    else
    {
        insertsort(a   keyindex   1, right - (keyindex   1)   1);
    }
}

4.非递归

非递归借助了数据结构栈的模拟:栈和队列的实现

1.过程

栈有先进后出的原则 ,所以我们先判断下是否符合区间值>1的条件,如果符合,则先将右边的右入栈,再入右边的左 ,其次入左边的右,再入,做左边的左 呈现出来则为 ,左边的左 ,右 ,右边的左,右

2.代码实现

代码语言:javascript复制
int   partsort(int* a, int  left, int right)
{
    if (left >= right)
    {
        return 0;
    }
    int begin = left;
    int end = right;
    int pivot = begin;
    int key = a[begin];
    while (begin < end)
    {
        while (a[end] >= key &amp;&amp; begin < end)
        {
            end--;
        }
        a[pivot] = a[end];
        pivot = end;

        while (a[begin] <= key &amp;&amp; begin < end)
        {
            begin  ;
        }
        a[pivot] = a[begin];//把大的放在右边的坑位中,自己形成新的坑位
        pivot = begin;
    }
    pivot = begin;//当begin==end时,循环结束,将关键字赋值给begin所在位置
    a[pivot] = key;//通过坑位分成两部分   [left    pivot-1] pivot [pivot 1   right]
    return pivot;
}
void quicksortNonR(int* a, int n)
{
    ST st;
    stackinit(&amp;st);
    stackpush(&amp;st, n - 1);//先将右 输入  再将左输入
    stackpush(&amp;st, 0);//输出时,则为左先输出 ,右再输出
    while (!stackempty(&amp;st))
    {
        int left = stacktop(&amp;st);
        stackpop(&amp;st);
        int right = stacktop(&amp;st);
        stackpop(&amp;st);
        int keyindex = partsort(a, left, right);// [left    keyindex-1] keyindex [keyindex 1   right]
        if (keyindex   1 < right)//栈符合先进后出的原则 
        {
            stackpush(&amp;st, right);//先入右区间的右 再入右区间的左
            stackpush(&amp;st, keyindex   1);//输出右区间的左 再输出右
        }
        if (left < keyindex - 1)//再入左区间的右  左区间的左
        {
            stackpush(&amp;st, keyindex - 1);//输出左区间的左,再输出右
            stackpush(&amp;st, left);
        }

    }
    stackdestroy(&amp;st);
}

二、归并排序

1.递归

1.过程

通过递归的方式使左半区间与右半区间有序 ,最后使整体区间有序

2.代码实现

代码语言:javascript复制
void mergesort(int* a, int left, int right, int* tmp)//
{
    if (left >= right)//当区间不存在或者只有一个时 返回
    {
        return;
    }
    int mid = (left   right) / 2;
    mergesort(a, left, mid, tmp);//左半区间
    mergesort(a, mid 1, right, tmp);//右半区间


    int begin1 = left;
    int end1 = mid;
    int begin2 = mid   1;
    int end2 = right;
    int index = left;
    while (begin1 <= end1 &amp;&amp; begin2 <= end2)//将小的的依次放入新的临时数组中
    {
        if (a[begin1] < a[begin2])
        {
            tmp[index] = a[begin1];
            index  ;
            begin1  ;
        }
        else
        {
            tmp[index] = a[begin2];
            index  ;
            begin2  ;
        }
    }
    while (begin1 <= end1)//如果此时的左区间未完全排完 则进入循环
    {
        tmp[index] = a[begin1];
        index  ;
        begin1  ;
    }
    while (begin2 <= end2)//如果此时的右区间未完全排完 则进入循环
    {
        tmp[index] = a[begin2];
        index  ;
        begin2  ;
    }
    int i = 0;
    for (i = 0; i <=right; i  )//将此时排好的临时数组传回原数组中
    {
        a[i] = tmp[i];
    }
}
void sort(int* a, int n)//归并排序  递归
{
    int left = 0;
    int right = n - 1;
    int* tmp = (int*)malloc(sizeof(int) * n);
    mergesort(a, left, right, tmp);
    free(tmp);
}

3.归并排序的时间复杂度

每次都分为 两个对半的区间 可以看作是一个满二叉树 2^h-1=N h=log N 当向上递归排序时,每一层的区间排序会遍历到所有数 n 时间复杂度为O(N) 即归并排序整体时间复杂度为O(N*log N)

4.递归的缺陷

如果栈深度太深,栈的空间不够用,可能会造成溢出

2.非递归

1.过程

采用循环,从之前的底层开始进行,一直 到整体有序 结束 假设此时数组中有8个数 通过 [i ,i gap-1] [i gap , i 2*gap-1] 每次都分为两个区间 则gap为1时就将 左边区间个数为1 与右边区间个数为1 进行排序 gap为2时就将 左边区间个数为2 与右边区间个数为2 进行排序 gap为4时就将 左边区间个数为4 与右边区间个数为4 进行排序

2.代码实现

代码语言:javascript复制
void mergesortNonR(int* a, int n)
{
    int* tmp = (int*)malloc(sizeof(int) * n);
    int gap = 1;
    int i = 0;
    while (gap < n)
    {
        for (i = 0; i < n; i  = 2 * gap)//以gap为间隔 分为 1   2   4
        {
            int begin1 = i;//[i  i gap-1] [i gap   i 2*gap-1]
            int end1 = i   gap - 1;
            int begin2 = i   gap;
            int end2 = i   2 * gap - 1;
            int index = i;
            if (begin2 >= n)//右区间可能不存在
            {
                break;
            }
            if (end2 >= n)//右区间算多了,修正下
            {
                end2 = n - 1;
            }
            while (begin1 <= end1 &amp;&amp; begin2 <= end2)//排序
            {
                if (a[begin1] < a[begin2])
                {
                    tmp[index] = a[begin1];
                    index  ;
                    begin1  ;
                }
                else
                {
                    tmp[index] = a[begin2];
                    index  ;
                    begin2  ;
                }
            }
            while (begin1 <= end1)
            {
                tmp[index] = a[begin1];
                index  ;
                begin1  ;
            }
            while (begin2 <= end2)
            {
                tmp[index] = a[begin2];
                index  ;
                begin2  ;
            }
            int j = 0;
            for (j = i; j <=end2; j  )
            {
                a[j] = tmp[j];
            }
        }
        gap *= 2;
    }
    free(tmp);
}

3.注意事项

左区间存在,右区间不存在时,左区间不需要归并

当将左区间的4个数 ,与右区间进行归并时 发现右区间只有三个数,第4个数不存在,所以修正回第三个数作为end2

当左区间的数不够gap所分的数,右区间不存在时,若将左区间拷贝回去会出现随机值 所以进行归并的才拷贝回去,没有进行的就不需要拷贝 ,所以将返回过程放入循环中

三、计数排序

1.思想

1.统计数,与其下标的大小对应,观察每个下标所对应的数量,直接输出就排好序了 ,此时为绝对映射位置

2.若数字过大 ,前面的空间就会浪费掉,所以避免这种情况的发生,则进行相对位置的映射

2.代码实现

时间复杂度为O(N)

代码语言:javascript复制
void countsort(int* a, int n)
{
    int i = 0;
    int j = 0;
    int max = a[0];
    int min = a[0];
    for (i = 0; i < n; i  )
    {
        if (max < a[i])
        {
            max = a[i];
        }
        if (min > a[i])
        {
            min = a[i];
        }
    }
    int range = max - min 1;//从0开始 所以多了一个数    
    int* count = (int*)malloc(sizeof(int) * range);
    memset(count, 0, sizeof(int) * range);//将count都初始化为0
    for (i = 0; i < n; i  )//统计次数
    {
        count[a[i] - min]  ;//a[i]-min为相对映射位置
    }
    for (i = 0; i < range; i  )//遍历 下标范围
    {
        while (count[i]--)//判断每个下标中的次数
        {
            a[j] = i min;
            j  ;
        }
    }
    free(count);
}

0 人点赞