array和list的排序算法对比(一):快速排序
2016-03-08 23:41
309 查看
一般来说,我们讨论排序都是针对数组结构。数组的特点是:可以很方便地进行随机访问,但是增删元素比较耗时。因此,针对数组的排序,通常会避免元素的增删操作,改为元素交换。同时,常采用二分的方法实现高效排序算法。
链表与数组相反,随机访问非常耗时,但增删元素很简单。因此链表的排序和数组也会有所不同。
这篇博客针对数组和链表的不同,分析了常用排序算法——快速排序在数组和链表中的实现。
注:我们规定排序的语义如下:
快速排序的思路是:以某个元素为切分点,把小于它的元素全部交换到前面,大于它的元素交换到后面,使切分点成为已排序元素,再对切分点前后进一步排序。
以首元素为切分点,代码如下:
快速排序的关键在于切分数组。上述代码中把数组分成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)大于等于切分元素
至此切分数组工作完成,即可进行下一步的递归。
此外,设链表的首节点为
此处
对照数组排序的思路,可以写出链表的快速排序:
链表的快速排序从总体上和数组的排序比较接近,也需要切分链表,但切分的方式则略有不同。
链表被分为以下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. 由于没有交换操作,链表的快速排序结构更加清晰。同时,链表的快速排序也是稳定排序。
以上数组快速排序算法是不稳定的,而链表快速排序算法是稳定的。但一般来说链表快速排序的效率低于数组,这是因为链表的结构更为复杂,且无法有效利用程序局部性。
数组和链表的快速排序平均时间复杂度均为O(nlogn)O(nlogn),平均空间复杂度为O(logn)O(logn)。
以上程序以首节点为切分点,在已排序数组或链表中会遇到最坏复杂度,最坏时间复杂度为O(n2)O(n^2),最坏空间复杂度为O(n)O(n)。
值得注意的是,数组可以采用随机选择切分节点的方式使最坏情况随机化,但链表没有随机访问特性,因此无法高效地随机选择切分节点。也就是说,链表的快速排序算法是有漏洞的。
链表与数组相反,随机访问非常耗时,但增删元素很简单。因此链表的排序和数组也会有所不同。
这篇博客针对数组和链表的不同,分析了常用排序算法——快速排序在数组和链表中的实现。
注:我们规定排序的语义如下:
sort(begin, end)表示对
[begin, end)之间的元素排序,包含begin,但不包含end。
1 数组的快速排序
本文待排序的int数组及排序函数如下:int num ; 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(nlogn),平均空间复杂度为O(logn)O(logn)。
以上程序以首节点为切分点,在已排序数组或链表中会遇到最坏复杂度,最坏时间复杂度为O(n2)O(n^2),最坏空间复杂度为O(n)O(n)。
值得注意的是,数组可以采用随机选择切分节点的方式使最坏情况随机化,但链表没有随机访问特性,因此无法高效地随机选择切分节点。也就是说,链表的快速排序算法是有漏洞的。
相关文章推荐
- Java 模拟Ajax上传文件
- 策略模式
- oracle中一些关于blob字段的操作
- Cucumber测试驱动开发
- DSA 算法
- 如何阅读他人的程序代码[转]
- hihoCoder Floyd算法
- Hadoop HA 搭建
- 0x09 文本相似性,词袋向量化
- 两份MySQL的配置
- 【程序员必备】sql语句大全
- for循环中变量i始终为length值的分析与解决
- 关于AsyncTask中的cancel方法
- nodejs-express-ejs-mongodb-mongoose 错误锦集
- codeforces 631D. Messenger kmp
- docker学习笔记5:利用commit命令创建镜像 和 删除本地镜像
- Linux学习笔记第一章
- Fragment之底层关键操作函数moveToState
- 稀疏表示与匹配追踪
- Android NDK环境搭建