array和list的排序算法对比(一):快速排序
来源:互联网 发布:麦多商城系统 源码 编辑:程序博客网 时间:2024/06/10 03:03
一般来说,我们讨论排序都是针对数组结构。数组的特点是:可以很方便地进行随机访问,但是增删元素比较耗时。因此,针对数组的排序,通常会避免元素的增删操作,改为元素交换。同时,常采用二分的方法实现高效排序算法。
链表与数组相反,随机访问非常耗时,但增删元素很简单。因此链表的排序和数组也会有所不同。
这篇博客针对数组和链表的不同,分析了常用排序算法——快速排序在数组和链表中的实现。
注:我们规定排序的语义如下: sort(begin, end)
表示对[begin, end)
之间的元素排序,包含begin,但不包含end。
1 数组的快速排序
本文待排序的int数组及排序函数如下:
int num[N];quick_sort(int *begin, int *end); //begin和end为指向数组元素的指针
快速排序的思路是:以某个元素为切分点,把小于它的元素全部交换到前面,大于它的元素交换到后面,使切分点成为已排序元素,再对切分点前后进一步排序。
以首元素为切分点,代码如下:
void quick_sort(int *begin, int *end){ /* quick sort */ if(begin == end) return; int *tmp = begin + 1; //扫描位置 int *partition = begin + 1; //大于等于begin的第一个元素 while(tmp != end){ if(*tmp < *begin){ //当前位置小于切分元素,则应将其与partition处的元素交换,并将partition后移一位 swap(*partition++, *tmp++); }else{ tmp++; } } partition--; swap(*partition, *begin); //begin放在切分点位置 quick_sort(begin, partition); quick_sort(partition + 1, end);}
快速排序的关键在于切分数组。上述代码中把数组分成4个部分:
1. begin元素
2. (begin, partition)为小于begin的元素
3. [partition, tmp)为大于等于begin的元素,
4. [tmp, end)为未排序元素
while循环用于处理tmp处元素,扩展第1和第2部分,缩减第3部分。处理方法是:若tmp小于begin元素,则tmp与partition处元素交换,再各自加1;若tmp大于等于begin元素,直接将tmp自加即可。
当tmp移动到end的时候,数组就只剩下前3个部分。然后,partition自减1,即为小于切分元素的最后一个元素。将partition元素和begin处元素交换,则数组的3部分如下:
1. [begin, partition)小于切分元素
2. partition处为切分元素
3. (partition, end)大于等于切分元素
至此切分数组工作完成,即可进行下一步的递归。
2 链表的快速排序
这里以一个简单的双向链表作为示例:
class Node{ int value; Node *next; Node *prev;};
此外,设链表的首节点为head
,尾节点为tail
,均不存储实际数据,即,如果一个链表有三个元素1,2,3,则链表的结构应为: head <-> Node(1) <-> Node(2) <-> Node(3) <-> tail
此处head
和tail
不存储数据,是为了在对链表进行操作时无需考虑边界情形。
对照数组排序的思路,可以写出链表的快速排序:
void quick_sort(Node *begin, Node *end){ Node *tmp = begin->next; Node *tmp_head = begin->prev; //begin的前一个元素(head的存在使得begin前必然有元素) while(tmp != end){ if(tmp->value < begin->value){ Node *s = tmp; tmp = tmp->next; //先移动指针,再操作s处元素 //把s从链表中取出来 s->prev->next = s->next; s->next->prev = s->prev; //把s插入到begin前面(即begin与其前一个元素之间,由于begin前至少还有head,所以这个语义是成立的) begin->prev->next = s; s->prev = begin->prev; s->next = begin; begin->prev = s; }else{ tmp = tmp->next; } } quick_sort(tmp_head->next, begin); quick_sort(begin->next, end);}
链表的快速排序从总体上和数组的排序比较接近,也需要切分链表,但切分的方式则略有不同。
链表被分为以下4部分:
1. [tmp_head->next, begin)小于切分元素
2. begin处为切分元素
3. [begin->next, tmp)大于等于切分元素
4. [tmp, end)尚未处理
与数组快速排序相同,链表排序也需要借助while循环后移tmp节点,并扩展1和3部分。
通过对比可以发现,链表快速排序与数组快速排序的不同点为:
1. 数组排序中,需要不断修正partition的位置,而链表排序中,切分点始终在begin处。
2. 数组排序中,为避免增删元素,需通过交换将小于切分元素的节点向前移动。而链表排序中,只需要将小于begin的节点从原位置删除,再插入到begin正前方即可。
3. 由于没有交换操作,链表的快速排序结构更加清晰。同时,链表的快速排序也是稳定排序。
3 小结
- 在数组和链表的排序中,需要充分考虑到数组和链表在随机访问和增删方面的复杂度,合理编写代码。
- 以上数组快速排序算法是不稳定的,而链表快速排序算法是稳定的。但一般来说链表快速排序的效率低于数组,这是因为链表的结构更为复杂,且无法有效利用程序局部性。
- 数组和链表的快速排序平均时间复杂度均为
O(nlogn) ,平均空间复杂度为O(logn) 。 - 以上程序以首节点为切分点,在已排序数组或链表中会遇到最坏复杂度,最坏时间复杂度为
O(n2) ,最坏空间复杂度为O(n) 。 - 值得注意的是,数组可以采用随机选择切分节点的方式使最坏情况随机化,但链表没有随机访问特性,因此无法高效地随机选择切分节点。也就是说,链表的快速排序算法是有漏洞的。
- array和list的排序算法对比(一):快速排序
- array和list排序算法对比(二):归并排序
- 排序算法(一)快速排序
- 排序算法(一):快速排序
- 排序算法(一)--快速排序
- 排序算法一(直接选择,堆排序,冒泡排序和快速排序)
- 快速排序和冒泡排序的时间复杂度分析(C++算法实现对比)
- 排序算法系列一:冒泡排序和快速排序
- 排序算法一:快速排序
- 排序算法:(一)快速排序
- 归并排序和快速排序的衍生问题(一)
- php实现排序算法(一) 冒泡排序 快速排序
- 计数排序,基数排序,桶排序和快速排序算法时间对比
- 插入排序、选择排序和快速排序的算法实现
- 必须知道的八大种排序算法【java实现】(一) 冒泡排序、快速排序
- 必须知道的八大种排序算法【java实现】(一) 冒泡排序、快速排序
- 排序算法(三)------冒泡排序和快速排序
- 算法-排序-交换排序(冒泡和快速排序)
- 【程序员必备】sql语句大全
- hihoCoder Floyd算法
- 两份MySQL的配置
- 0x09 文本相似性,词袋向量化
- Java 模拟Ajax上传文件
- array和list的排序算法对比(一):快速排序
- 策略模式
- Intent应用详解
- cocos2dx 基础
- Java的列表排序问题
- Android数据库SQLite操作详解及LitePal用法详解(一)
- linux下mysql的简单配置及监控
- 【ios】关于UIImageView的一个坑
- 内网穿透&UDP打洞