第一次在这里发,排版不行,见谅啊,sorry,我就没排版。
对于快速排序,我以前只是用来排数组,从来没有排过链表,直到有一天,我使用了算法导论上 N.Lomoto版本的快速排序,发现这个版本也许可以用来排链表,于是就尝试了下,发现真的可以.
在此先说一下 N.Lomoto 版本和国内普遍使用的 Hoare(霍尔)版本的区别.
霍尔版本应该算是快速排序的正式版本,他改进了之前的算法,然后称之为快速排序,而后来 N.Lomoto 做了优化,即写在算法导论一书中的快速排序.
这是快速排序的基本模样,而这两个算法不同在于 partition:
QUICKSORT(A, p, r)
1 if p < r
2 then q ← PARTITION(A, p, r) //关键
3 QUICKSORT(A, p, q - 1)
4 QUICKSORT(A, q + 1, r)
HOARE版本的 partition:
HOARE-PARTITION(A, p, r)
1 x ← A[p]
2 i ← p - 1
3 j ← r + 1
4 while TRUE
5 do repeat j ← j - 1
6 until A[j] ≤ x
7 repeat i ← i + 1
8 until A[i] ≥ x
9 if i < j
10 then exchange A[i] ↔ A[j]
11 else return j
算法导论版本的 partition:
PARTITION(A, p, r)
1 key ← A[r] //以最后一个元素,A[r]为主元
2 i ← p - 1
3 for j ← p to r - 1 //注,j 从p指向的是r-1,不是r。
4 do if A[j] ≤ key and i != j // 原来版本没有 I != j ,加上是为了避免不必要的交换
5 then i ← i + 1
6 exchange A[i] <-> A[j]
7 exchange A[i + 1] <-> A[r] //即 key
8 return i + 1
从上面可以看出,霍尔版本是双向扫描的,而算法导论的版本是单向的,两个算法各有利弊吧, 但是本人习惯用算法导论版本.现在就重点讲述这个版本.
首先,第一步,他将数组A的最后一个下标作为一个比较值,姑且称之为key,这个过程我们可以这么认为,我们拿最后一个元素作为key, 现在,我们想把整个数组分为2部分, key的左边的值要小于 key, 而key的右边的值要大于key.为了实现这个过程, 我们用 j从p到r来一次循环,而i (初始值为 P-1) ,i的作用,是用来记录比key小的左边的坐标,也就是说在小于等于i的坐标里,其值肯定是小于等于 key的.
下面用一个例子来描述一下
数组 7 4 1 8 2
key
7 4 1 8 2
i j
i j
i j // A[j] <= key, i++, 并且交换 A[i] <-> A[j], 因为此时,j左边的肯定是比key大的
1 4 7 8 2
i j
i j
i j
1 2 7 8 4 //到此,j已经到达了下标r,即数组的最后,循环结束,这时,下标小于等于 i 的值是肯定小于 key的,所以此时
i i+1 把key值放到正确的位置,即下标 i+1 处, 交换 A[i+1] <-> A[r]
最后return i+1,至此,partition 结束,然后在递归这个过程.
此过程可以简单的理解为把数组最后一个元素放到一个正确的位置,使其左边的元素不大于他,使其右边的元素不小于他, 由此将数组分割为2个部分.
下标J的作用是把数组从头到尾遍历,而下标i的作用是记录比key值小的最右边的下标.
下面是一个简单的代码实现.
#include <stdio.h>
void swap_array(int *a, int *b);
void sort_quick(int *array,int l, int r);
int sort_quick_apart(int *array, int l, int r);
void sort_quick(int *array,int l, int r)
{
if(l<r)
{
int p = sort_quick_apart(array, l, r);
sort_quick(array, l, p-1);
sort_quick(array, p+1, r);
}
}
int sort_quick_apart(int *array, int l, int r)
{
long key = array[r];
int i = l -1;
for(int j=l; j<r; j++)
{
if( array[j] <= key)
{
i ++;
swap_array(&array[i], &array[j]);
}
}
swap_array(&array[i+1], &array[r]);
return i+1;
}
void swap_array(int *a, int *b)
{
int t = *a;
*a = *b;
*b =t;
}
/* */
void print_array(int *arr, int num)
{
for(int i=0; i<num; i++)
{
printf("%d ", arr[i] /* i%5 ==4 ?'\n' : ' ' */ );
}
printf("\n");
}
int main()
{
int array[10]={ 9,3,2,5,1,8,7,4,0,6 };
printf("before sort\n");
print_array(array,10);
sort_quick(array,0,9);
printf("after sort\n");
print_array(array,10);
}
输出结果结果:
before sort
9 3 2 5 1 8 7 4 0 6
after sort
0 1 2 3 4 5 6 7 8 9
到此,我们的为链表排序的铺垫数组排序就先粗略的讲完了,下面才是我们的主题,为链表排序(单向链表,当然双向链表其实更可以).
单向链表的排序,很大程度上是跟这数组排序时很相似的,只是稍作修改.
Partition现在只需要2个参数,第一个是链表的头指针,第2个是尾部.
由于我们是单向链表,所以我们把链表的第一个元素来作为 key 值.
typedef struct nnode{
int elem;
struct nnode *next;
}Node; /*链表的元素*/
QUICKSORT(head, tail )
1 if head != tail
2 then p ← PARTITION( head, tail) //关键
3 QUICKSORT(head, p)
4 QUICKSORT(p->next, tail)
PARTITION( head, tail)
1 key ← head->elem //链表的第一个元素为 key
2 p1 ← head->next // p1作用类似数组排序中的i,用来记录key左边的
P2 ← head // p2作用类似j,用来遍历
3 while p1 != tail
4 if p1->elem ≤ key
5 then p2 ← p2->next
6 exchange p1->elem <-> p2->elem //交换元素
7 P1 ← p1->next
8 exchange p2->elem <-> head->elem
9 return p2
代码:
#include <stdio.h>
#include <time.h>
/* 链表的快速排顺 */
typedef struct nnode{
int elem;
struct nnode *next;
}Node;
Node *listInit()
{
Node *p = (Node*)malloc(sizeof(Node));
p->next = 0;
p->elem = -1;
return p;
}
/* 这个是个头指针而已,不算是元素 */
void listAdd(Node *ls, int elem)
{
if(!ls)
return;
Node *p = ls;
while(p->next)
p = p->next;
p->next = (Node *)malloc(sizeof(Node));
p->next->elem = elem;
p->next->next = 0;
}
void print_list(Node *ls)
{
if(!ls)
return;
Node *p =ls->next;
while(p)
{
printf("%d ", p->elem);
p = p->next;
}
printf("\n");
}
Node * sortListApart(Node *phead, Node *pend); /* 分隔*/
void swapListApart(Node *p1, Node *p2); /* 元素交换*/
/* sort list quick */
void sortListQuick(Node *phead, Node *pend)
{
if( phead != pend)
{
Node *p = sortListApart(phead, pend);
sortListQuick(phead, p);
sortListQuick(p->next, pend);
}
}
Node * sortListApart(Node *phead, Node *pend)
{
Node *p1 = phead->next;
Node *p2 = phead;
int key = phead->elem;
while(p1 != pend)
{
if( p1->elem <= key)
{
p2 = p2->next;
swapListApart(p1, p2);
}
p1 = p1->next;
}
swapListApart(p2, phead);
return p2;
}
void swapListApart(Node *p1, Node *p2)
{
int tmp = p1->elem;
p1->elem = p2->elem;
p2->elem = tmp;
}
int main()
{
/* 快速排序,对于链表 */
Node *phead = listInit();
listAdd(phead, 56);
listAdd(phead, 32);
listAdd(phead, 5);
listAdd(phead, 12);
listAdd(phead, 22);
listAdd(phead, 4);
listAdd(phead, 2);
listAdd(phead, 9);
listAdd(phead, 232);
listAdd(phead, 51);
listAdd(phead, 28);
listAdd(phead, 32);
listAdd(phead, 37);
print_list(phead);
sortListQuick(phead->next, NULL);
print_list(phead);
free(phead);
}
运行结果:
56 32 5 12 22 4 2 9 232 51 28 32 37
2 4 5 9 12 22 28 32 32 37 51 56 232