各种排序总结

    本文对各种排序做一个总结(这里不简述具体的过程),包括冒泡排序、选择排序、快速排序、归并排序、堆排序。分别从时间复杂度、空间复杂度、算法的最坏情况以及稳定性方面分析,递归的尽量会附带非递归实现。

1. 冒泡排序

    冒泡排序是一种很简单的排序算法,这里直接上代码(从小到大排序):

bool flag = true;//如果在某一趟排序中数列已经有序,则结束排序过程
for(int i = 0;i < n-1 && flag; ++i){
    flag = false;
    for(int j = 0; j < n-i-1; ++j){//减一是为了实现g[j+1]
        if(g[j] > g[j+1]){
            swap(g[j], g[j+1]);
            flag = true;
        }
    }
}

时间复杂度:时间复杂度是O(n^2),假设排序的数有n个,遍历一趟的复杂度是O(n),需要遍历n-1次,所以时间复杂度为O(n^2)。

空间复杂度:O(1),因为不需要额外的存储空间。

最坏情况:要排序的数是逆序排好序的。

稳定性:冒泡排序是稳定的算法,因为它实现相邻两个元素之间的交换,没有改变元素的相对位置,所以满足稳定性。

2. 选择排序

    选择排序是一种比较好理解的排序,可以说是冒泡排序的兄弟了,只是排序方式不一样,下面直接给出代码:

//选择排序
void Select_Sort(int a[], int n){
    int Min;
    for(int i = 0;i < n-1; ++i){//n-1次选择就可以了
        Min = i;
        for(int j = i+1; j < n; ++j){//从i - n之间选择一个最小的放在 i 处。
            if(a[j] < a[Min]){
                Min = j;
            }
        }
        if(Min != i){
            swap(a[i], a[Min]);
        }
    }
}

时间复杂度:O(n^2),因为每次遍历的复杂度是O(n)的,一共遍历n-1次,所以复杂度是O(n^2)。

空间复杂度:O(1)

最坏情况:排序的数和复杂度没关系,属于佛系。

稳定性:选择排序是不稳定的排序,例如有5 8 5 2 9 ,5个元素按从小到大排序,当第一趟排序后形成2 8 5 5 9 ,两个5的相对位置最终排好序后发生了变化。

3. 快速排序

    快速排序(Quick Sort)使用分治法策略它的基本思想是:选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分;其中一部分的所有数据都比另外一部分的所有数据都要小。然后,再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。快速排序经常容易被改造用在其它的一些方面,如求数组中第k大的数。快排的基准值的选取会对排序结果有一定影响。快速排序是面试中常考的类型,需要关注非递归的方式。

    这里不对算法进行相应描述,直接看代码(其中基准值是随机选取的):

/*快速排序
 * l ----- 序列左边下标
 * t ----- 序列右边下标
 * a[] --- 数组
 */
int Partition(int a[], int l, int t){//划分
    int idx = l + rand()%(t - l + 1);//基准值是在随机选取的
    swap(a[l], a[idx]);
    int i = l;
    int j = t;
    int x = a[i];
    while(i < j){
        while(i < j && a[j] > x)
            j--;
        if(i < j)
            a[i++] = a[j];
        while(i < j && a[i] < x)
            i++;
        if(i < j)
            a[j--] = a[i];
    }
    a[i] = x;
    return i;
}
void Quick_Sort(int a[], int l, int t){
    if(l < t){
        int mid = Partition(a, l, t);//划分,返回基准值下标
        Quick_Sort(a, l, mid-1);//递归调用
        Quick_Sort(a, mid+1, t);//递归调用
    }
}

非递归代码:

typedef pair<int, int> Pair;
int Partition(int a[], int lt, int rt){//划分
    int i = lt;
    int j = rt;
    int rm = i + rand()%(j - i + 1);//基准值是在随机选取的
    swap(a[i], a[rm]);
    int x = a[i];
    while(i < j){
        while(i < j && a[j] > x)
            j--;
        if(i < j)
            a[i++] = a[j];
        while(i < j && a[i] < x)
            i++;
        if(i < j)
            a[j--] = a[i];
    }
    a[i] = x;
    return i;
}
void Quick_Sort(int a[], int l, int t){
    stack<Pair>S;
    S.push(Pair(l, t));
    while(!S.empty()){
        int lt = S.top().first;
        int rt = S.top().second;
        S.pop();
        int mid = Partition(a, lt, rt);
        if(mid+1 < rt)
            S.push(Pair(mid+1, rt));
        if(lt < mid-1)
            S.push(Pair(lt, mid-1));
    }
}

这里增加一种用链表实现快速排序(面试中可能会问到):

思想:用链表排序主要的困难是如何实现链表的基于基准值的划分,这里假设传入的链表的头和尾节点分别为Head和end(这里需要同时给出头节点和尾节点),因为不能像数组那样随机存储,所以这里以头节点Head值为基准值,设置两个指针(个人感觉:用到链表的一般都是设置2个指针,当然有可能是一个或者三个,四个应该基本不会有吧!)small 和 p。

  1. small 指向Head,p 指向Head->next,p用于向下遍历,寻找小于Head->data值的节点;
  2. 当p遍历到一个节点的值小于Head->data的值的时候,small先进行small = small->next,因为small指向的是小于Head->data(初始时指向Head->data),进行small = small->next后small指向的值就大于等于Head->data的值了,然后将small指向的值与p指向的值进行交换。p = p->next;
  3. 继续进行(2),知道p遍历完所有的值;
  4. 将Head->data的值与small节点的值交换就可以了;

建议按照下面代码模拟一下就很明白了,可能这样说不是很明白。

struct ListNode{
    int data;
    ListNode *next;
    ListNode(int _data = 0):data(_data),next(NULL){}
};
void Quick_Sort(ListNode *head, ListNode *end){
    if(head == NULL || head == end)             //如果头指针为空或者链表为空,直接返回
        return ;
    ListNode *p = head -> next;                  //用来遍历的指针
    ListNode *small = head;
    while( p != end->next){
        if(p -> data < head -> data){      //对于小于轴的元素放在左边
            small = small -> next;
            swap(small->data, p->data);
        }
        p = p->next;
    }
    swap(head->data, small->data);         //遍历完后,对左轴元素与small指向的元素交换
    Quick_Sort(head, small);               //对左右进行递归
    Quick_Sort(small->next, end);
}

时间复杂度:快速排序最坏时间复杂度是O(n^2),在(1)数组中的元素全部相同,(2)数组已经排好序或逆序(这要看选取的基准值,如果总是选取最左边的作为基准值则是O(n^2))这两类情况下快速排序会达到最坏的情况。从这里可以看出选取基准值很重要,如果基准值随机选取,快排很难达到最坏情况。平均时间复杂度是O(n*log2(n)),可以这样理解:快排是采用分治的策略,可以把这个过程看成一个二叉树,则遍历一层的时间复杂度是O(n),树的高度为log2(n+1),则复杂度为O(n*log2(n))。

空间复杂度:O(logn)~O(n),这里看的是递归的深度。

稳定性:快排是一种不稳定的算法,因为在选取基准值相互交换的时候,元素的相对位置会变化。

4. 归并排序

    归并排序算法是典型的分治算法,其算法思想是先将数组对半分开,然后将得到的两个数组再对半分开,一直到只有一个元素为止,然后再进行两两合并,最后合并为一个大的数组,具体思路可看下图。

代码实现:

void Merge(int a[], int lt, int rt, int p[]){//p是临时数组
    int mid = (rt - lt)/2 + lt;
    int i = lt, j = mid + 1;
    int k = 0;
    while(i <= mid && j <= rt){
        if(a[i] <= a[j]){
            p[k++] = a[i++];
        }else {
            p[k++] = a[j++];
        }
    }
    while(i <= mid){
        p[k++] = a[i++];
    }
    while(j <= rt){
        p[k++] = a[j++];
    }
    for(i = 0; i < k; ++i){
        a[lt+i] = p[i];
    }
}
void MergeSort(int a[], int lt, int rt, int p[]){
    if(lt < rt){
        int mid = (rt - lt)/2 + lt;
        MergeSort(a, lt, mid, p);
        MergeSort(a, mid+1, rt, p);
        Merge(a, lt, rt, p);
    }
}

时间复杂度:归并排序最好、最坏、平均时间复杂度都是O(nlogn),同样可以把归并排序的过程看成一个完全二叉树(这里要和快速排序区分),划分的时间复杂度为O(logn),每层合并的时间复杂度为O(n),所以总的时间复杂度为O(nlogn)。

最坏情况:复杂度和排序的数的顺序无关,佛系。

空间复杂度:O(n),用到一个n的辅助数组。

稳定性:归并排序是稳定的。

5. 堆排序

    堆排序(Heap_Sort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利数组特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。常用于求前1-k的所有数字。

 堆排序的代码如下:

//向下调整
void Adjust_Heap(int a[], int idx, int Len){
    while(idx*2+1 < Len){
        int temp = idx*2+1;
        if(temp+1 < Len && a[temp+1] > a[temp]){
            temp++;
        }
        if(a[idx] < a[temp]){
            swap(a[idx], a[temp]);
            idx = temp;
        }else break;
    }
}

void Heap_Sort(int a[], int n){
    //建立大顶堆
    for(int i = (n-1)/2; i >= 0; --i){
        Adjust_Heap(a, i, n);
    }
    //排序
    for(int i = 0;i < n; ++i){
        swap(a[0], a[n-i-1]);
        Adjust_Heap(a, 0, n-i-1);
    }

}

时间复杂度:建堆的复杂度是O(n)(这个需要计算一下),排序的复杂度是n * lgn 所以是O(nlog2(n))。

空间复杂度:O(1)

稳定性:堆排序是不稳定的。举个栗子:1 2 2 的情况就不满足(从小到大排序),因为排序后两个2的相对位置变了。

                 

Reference :

https://blog.csdn.net/jiajing_guo/article/details/69388331

 

 

  • 5
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
各种数据结构的排序算法适用于不同的排序需求。以下是一些常见的数据结构和对应的排序算法: 1. 数组(Array): 数组是最常见的数据结构之一,适用于大多数排序算法。例如,插入排序、选择排序、冒泡排序和快速排序等算法都可以用于对数组进行排序。 2. 链表(Linked List): 链表是另一种常见的数据结构,由节点组成,每个节点包含一个元素和指向下一个节点的指针。对链表进行排序的常用算法有插入排序和归并排序。 3. 栈(Stack)和队列(Queue): 栈和队列是特殊的线性数据结构,栈是后进先出(LIFO),队列是先进先出(FIFO)。对于栈和队列,一般不需要进行排序。 4. 堆(Heap): 堆是一种二叉树结构,具有特定的性质,例如最大堆或最小堆。对堆进行排序的算法有堆排序。 5. 树(Tree): 树是一种非线性的数据结构,包括二叉树、平衡二叉树、红黑树等。对树进行排序的算法有二叉树排序、AVL树排序等。 6. 图(Graph): 图是一种由节点和边组成的数据结构,适用于某些特定的排序算法,例如拓扑排序总结来说,各种数据结构可以使用不同的排序算法进行排序,选择适当的排序算法取决于数据结构的特点和排序需求。<span class="em">1</span> #### 引用[.reference_title] - *1* [数据排序MapReduce实例](https://download.csdn.net/download/qq_37647812/88251299)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Linux猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值