单链表的快速排序

本文介绍了一种适用于单链表的快速排序算法实现方法。通过选取链表首个节点作为基准,将小于基准的节点放入左侧子链表,大于基准的放入右侧子链表。并详细解释了遍历和调整链表的过程,以及如何通过递归和迭代提高排序效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单链表的快排序和数组的快排序基本思想相同,同样是基于划分,但是又有很大的不同:单链表不支持基于下标的访问。故书中把待排序的链表拆分为2个子链表。为了简单起见,选择链表的第一个节点作为基准,然后进行比较,比基准小得节点放入左面的子链表,比基准大的放入右边的子链表。在对待排序链表扫描一遍之后,左边子链表的节点值都小于基准的值,右边子链表的值都大于基准的值,然后把基准插入到链表中,并作为连接两个子链表的桥梁。然后分别对左、右两个子链表进行递归快速排序,以提高性能。

        但是,由于单链表不能像数组那样随机存储,和数组的快排序相比较,还是有一些需要注意的细节:

       1、支点的选取,由于不能随机访问第K个元素,因此每次选择支点时可以取待排序那部分链表的头指针。

       2、遍历量表方式,由于不能从单链表的末尾向前遍历,因此使用两个指针分别向前向后遍历的策略实效,

                事实上,可以可以采用一趟遍历的方式将较小的元素放到单链表的左边。具体方法为:

                1)定义两个指针pslow,pfast,其中pslow指向单链表的头结点,pfast指向单链表头结点的下一个结点;

                2)使用pfast遍历单链表,每遇到一个比支点小的元素,就令pslow=pslow->next,然后和pslow进行数据交换。

       3、交换数据方式,直接交换链表数据指针指向的部分,不必交换链表节点本身。

       基于上述思想的单链表快速排序实现如下:

  1. /**   
  2. ** 单链表的快速排序   
  3. ** author :liuzhiwei 
  4. ** date   :2011-08-07   
  5. **/  
  6.   
  7. #include<iostream>   
  8. #include<ctime>   
  9. using namespace std;  
  10. //单链表节点   
  11.   
  12. struct SList  
  13. {  
  14.     int data;  
  15.     struct SList* next;  
  16. };  
  17.   
  18. void bulid_slist(SList** phead, int n)    //指向指针的指针   
  19. {  
  20.     int i;  
  21.     SList* ptr = *phead;  
  22.     for(i = 0; i < n; ++i)  
  23.     {  
  24.         SList* temp = new SList;  
  25.         temp->data = rand() % n;   //产生n个n以内的随机数   
  26.         temp->next = NULL;  
  27.         if(ptr == NULL)  
  28.         {  
  29.             *phead = temp;  
  30.             ptr = temp;  
  31.         }  
  32.         else  
  33.         {  
  34.             ptr->next = temp;  
  35.             ptr = ptr->next;  
  36.         }  
  37.     }  
  38. }  
  39.   
  40. void print_slist(SList* phead)   //输出链表   
  41. {  
  42.     SList *ptr = phead;  
  43.     while(ptr)  
  44.     {  
  45.         printf("%d ", ptr->data);  
  46.         ptr = ptr->next;  
  47.     }  
  48.     printf("\n");  
  49. }  
  50.   
  51. void my_swap(int *a,int *b)  
  52. {  
  53.     int temp;  
  54.     temp=*a;  
  55.     *a=*b;  
  56.     *b=temp;  
  57. }  
  58.   
  59. void sort_slist(SList* phead, SList* pend)    //将头指针为phead,尾指针为pend的链表进行排序   
  60. {  
  61.     if(phead == NULL)  
  62.         return ;  
  63.     if(phead == pend)  
  64.         return ;  
  65.     SList *pslow = phead;  
  66.     SList *pfast = phead->next;  
  67.     SList *ptemp = phead;  
  68.     while(pfast != pend)  
  69.     {  
  70.         if(pfast->data < phead->data)        //每次都选择待排序链表的头结点作为划分的基准   
  71.         {  
  72.             ptemp = pslow;          //ptemp始终为pslow的前驱结点   
  73.             pslow = pslow->next;  
  74.             my_swap(&pslow->data , &pfast->data);       //pslow指针指向比基准小的结点组成的链表   
  75.         }  
  76.         pfast = pfast->next;  
  77.     }  
  78.   
  79.     my_swap(&pslow->data , &phead->data);  //此时pslow指针指向比基准小的结点组成的链表的最后一个结点,也就是基准的位置,所以要与基准(head结点)交换   
  80.     sort_slist(phead , pslow);             //ptemp为左右两部分分割点(基准)的前一个结点   
  81.     sort_slist(pslow->next , NULL);        //右部分是比基准大的结点组成的链表   
  82. }  
  83.   
  84. void destroy_slist(SList* phead)  
  85. {  
  86.     SList* ptr = phead;  
  87.     while(ptr)  
  88.     {  
  89.         SList* temp = ptr;  
  90.         ptr = ptr->next;  
  91.         delete temp;  
  92.     }  
  93. }  
  94.   
  95. int main(void)  
  96. {  
  97.     srand(time(NULL));  
  98.     printf("Before sort single list\n");  
  99.     SList* phead = NULL;  
  100.     bulid_slist(&phead, 100);  
  101.     print_slist(phead);  
  102.     printf("After sort single list\n");  
  103.     sort_slist(phead, NULL);  
  104.     print_slist(phead);  
  105.     destroy_slist(phead);  
  106.     system("pause");  
  107.     return 0;  
  108. }  
/**  
** 单链表的快速排序  
** author :liuzhiwei
** date   :2011-08-07  
**/

#include<iostream>
#include<ctime>
using namespace std;
//单链表节点

struct SList
{
    int data;
    struct SList* next;
};

void bulid_slist(SList** phead, int n)    //指向指针的指针
{
    int i;
    SList* ptr = *phead;
    for(i = 0; i < n; ++i)
    {
        SList* temp = new SList;
        temp->data = rand() % n;   //产生n个n以内的随机数
        temp->next = NULL;
        if(ptr == NULL)
        {
            *phead = temp;
            ptr = temp;
        }
        else
        {
            ptr->next = temp;
            ptr = ptr->next;
        }
    }
}

void print_slist(SList* phead)   //输出链表
{
    SList *ptr = phead;
    while(ptr)
    {
        printf("%d ", ptr->data);
        ptr = ptr->next;
    }
    printf("\n");
}

void my_swap(int *a,int *b)
{
    int temp;
    temp=*a;
    *a=*b;
    *b=temp;
}

void sort_slist(SList* phead, SList* pend)    //将头指针为phead,尾指针为pend的链表进行排序
{
    if(phead == NULL)
        return ;
    if(phead == pend)
        return ;
    SList *pslow = phead;
    SList *pfast = phead->next;
    SList *ptemp = phead;
    while(pfast != pend)
    {
        if(pfast->data < phead->data)        //每次都选择待排序链表的头结点作为划分的基准
        {
            ptemp = pslow;          //ptemp始终为pslow的前驱结点
            pslow = pslow->next;
            my_swap(&pslow->data , &pfast->data);       //pslow指针指向比基准小的结点组成的链表
        }
        pfast = pfast->next;
    }

    my_swap(&pslow->data , &phead->data);  //此时pslow指针指向比基准小的结点组成的链表的最后一个结点,也就是基准的位置,所以要与基准(head结点)交换
    sort_slist(phead , pslow);             //ptemp为左右两部分分割点(基准)的前一个结点
    sort_slist(pslow->next , NULL);        //右部分是比基准大的结点组成的链表
}

void destroy_slist(SList* phead)
{
    SList* ptr = phead;
    while(ptr)
    {
        SList* temp = ptr;
        ptr = ptr->next;
        delete temp;
    }
}

int main(void)
{
    srand(time(NULL));
    printf("Before sort single list\n");
    SList* phead = NULL;
    bulid_slist(&phead, 100);
    print_slist(phead);
    printf("After sort single list\n");
    sort_slist(phead, NULL);
    print_slist(phead);
    destroy_slist(phead);
    system("pause");
    return 0;
}

        第二种方法:

        选择链表的第一个节点作为基准,然后进行比较,比基准小得节点放入左面的子链表,比基准大的放入右边的子链表。在对待排序链表扫描一遍之后,左面子链表的节点值都小于基准的值,右边子链表的值都大于基准的值,然后把基准插入到链表中,并作为连接两个子链表的桥梁。然后根据左、右子链表中节点数,选择较小的进行递归快速排序,而对数目较多的则进行迭代排序。

      排序函数中使用的变量如下:

struct node *right;   //右边子链表的第一个节点

struct node **left_walk, **right_walk;    //作为指针,把其指向的节点加入到相应的子链表中

struct node *pivot, *old;    //pivot为基准, old为循环整个待排序链表的指针

核心代码如下:

for (old = (*head)->next; old != end; old = old->next) {

      if (old->data < pivot->data) {  //小于基准,加入到左面的子链表,继续比较

             ++left_count;

         *left_walk = old;            //把该节点加入到左边的链表中,

         left_walk = &(old->next);

} else {                      //大于基准,加入到右边的子链表,继续比较

         ++right_count;

             *right_walk = old;          

             right_walk = &(old->next);

      }

}

head为struct node **类型,指向链表头部,end指向链表尾部,可为NULL,这段程序的重点在于指针的指针的用法,*left_walk为一个指向node节点的指针,说的明白点*left_walk的值就是node节点的内存地址,其实还有一个地方也有node的地址,那就是指向node的节点的next域,故我们可以简单的认为*left_walk = old就是把指向node节点的节点的next域改为节点old的地址,这样可能造成两种情况:一种就是*left_walk本来就指向old节点,这样就没有改变任何改变,另一种则是改变了*right_walk指向节点的前一个节点的next域,使其指向后部的节点,中间跳过了若干个节点,不过在这里这样做并不会造成任何问题,因为链表中的节点要么加入到左面的子链表中,要么加入到右面的子链表中,不会出现节点丢失的情况。

下面用图示说明下上面的问题:

这里假设链表的值一次是5、2、4、6、1。根据程序首先head = left_walk指向值为5的节点,old指向值为2的节点,2小于5,所以加入2到左面的子链表中,*left_walk=old,我们知道,*left_walk指向的是第一个节点,这样做改变了head指针值,使其指向第二个节点,然后left_walk后移,old后移,4同样小于5,故继续上述操作,但是这是*left_walk和old指向的是同一个节点,没有引起任何变化,left_walk和old后移,6大于5,这时不同就出现了,要把其加入到右边的子链表中,故是*right_walk = old,其实right_walk初试化为&right,这句话相当于right = old,即令old当前指向的节点作为右边子链表的第一个节点,以后大于基准的节点都要加入到这个节点中,且总是加入到尾部。此时right_walk,和old后移,1小于5应该加入到左边的子链表中,*left_walk = old,此时*left_walk指向6,故此语句的作用是更改节点4的next值,把其改为1的地址,这样6就从原来的链表中脱钩了,继续left_walk和old后移到9节点,应加入到右边的子链表中,此时*right_walk指向1,故把9节点加入到6节点的后面。

这就是基本的排序过程,然而有一个问题需要搞明白,比如有节点依次为struct node *a, *b, *c,node **p , p = &b,如果此时令*p = c,即实际效果是a->next = c;我们知道这相当于该a的next域的值。而p仅仅是一个指针的指针,它是指向b所指向的节点的地址的指针,那么当我们更改*p的值的时候怎么会改到了a的next呢(这个可以写程序验证下,确实如此)?其实并非如此,我们仔细的看看程序,left_walk初始化为head,那么第一次执行*left_walk是把head指向了左边链表的起始节点,然后left_walk被赋值为&(old->next),这句话就有意思了,我们看一看下面在执行*left_walk=old时的情况,可以简单的来个等价替换,*left_walk = old也就相当于*&(old->next) = old,即old->nex = old,不过这里的old可不一定是old->next所指向的节点,应为left_walk和right_walk都指向它们的old节点,但是却是不同的。

算法到这里并没有完,这只是执行了一次划分,把基准放入了正确的位置,还要继续,不过下面的就比较简单了,就是递归排序个数比较小的子链表,迭代处理节点数目比较大的子链表。

        完整的代码如下:

  1. #include <stdio.h>      
  2. #include <stdlib.h>      
  3. #include <time.h>      
  4. //链表节点      
  5. struct node  
  6. {  
  7.     int data;     
  8.     struct node *next;     
  9. };     
  10. //链表快排序函数   
  11. void QListSort(struct node **head,struct node *h);     
  12. //打印链表      
  13. void print_list(struct node *head)  
  14. {  
  15.     struct node *p;     
  16.     for (p = head; p != NULL; p = p->next)  
  17.     {     
  18.         printf("%d ", p->data);     
  19.     }     
  20.     printf("\n");     
  21. }     
  22. int main(void)  
  23. {  
  24.     struct node *head = NULL;     
  25.     struct node *p;     
  26.     int i;     
  27.     /**   
  28.     * 初始化链表   
  29.     */   
  30.     srand((unsigned)time(NULL));     
  31.     for (i = 1; i < 11; ++i)  
  32.     {     
  33.         p = (struct node*)malloc(sizeof(struct node));     
  34.         p->data = rand() % 100 + 1;  
  35.         if(head == NULL)  
  36.         {  
  37.             head = p;  
  38.             head->next = NULL;  
  39.         }  
  40.         else  
  41.         {  
  42.             p->next = head->next;  
  43.             head->next = p;  
  44.         }  
  45.     }  
  46.     print_list(head);  
  47.     printf("---------------------------------\n");  
  48.     QListSort(&head,NULL);  
  49.     print_list(head);  
  50.     system("pause");  
  51.     return 0;     
  52. }     
  53.   
  54. void QListSort(struct node **head, struct node *end)  
  55. {  
  56.     struct node *right;     
  57.     struct node **left_walk, **right_walk;     
  58.     struct node *pivot, *old;     
  59.     int count, left_count, right_count;     
  60.     if (*head == end)  
  61.         return;     
  62.     do {     
  63.         pivot = *head;     
  64.         left_walk = head;  
  65.         right_walk = &right;     
  66.         left_count = right_count = 0;     
  67.         //取第一个节点作为比较的基准,小于基准的在左面的子链表中,      
  68.         //大于基准的在右边的子链表中      
  69.         for (old = (*head)->next; old != end; old = old->next)  
  70.         {     
  71.             if (old->data < pivot->data)  
  72.             {      //小于基准,加入到左面的子链表,继续比较      
  73.                 ++left_count;     
  74.                 *left_walk = old;            //把该节点加入到左边的链表中,      
  75.                 left_walk = &(old->next);     
  76.             }  
  77.             else  
  78.             {                         //大于基准,加入到右边的子链表,继续比较      
  79.                 ++right_count;     
  80.                 *right_walk = old;                
  81.                 right_walk = &(old->next);     
  82.             }     
  83.         }     
  84.         //合并链表      
  85.         *right_walk = end;       //结束右链表      
  86.         *left_walk = pivot;      //把基准置于正确的位置上      
  87.         pivot->next = right;     //把链表合并      
  88.         //对较小的子链表进行快排序,较大的子链表进行迭代排序。      
  89.         if(left_walk > right_walk)  
  90.         {  
  91.             QListSort(&(pivot->next), end);     
  92.             end = pivot;     
  93.             count = left_count;     
  94.         }  
  95.         else  
  96.         {     
  97.             QListSort(head, pivot);     
  98.             head = &(pivot->next);     
  99.             count = right_count;     
  100.         }     
  101.     }  
  102.     while (count > 1);      
  103. }  
#include <stdio.h>   
#include <stdlib.h>   
#include <time.h>   
//链表节点   
struct node
{
	int data;   
	struct node *next;   
};   
//链表快排序函数
void QListSort(struct node **head,struct node *h);   
//打印链表   
void print_list(struct node *head)
{
	struct node *p;   
	for (p = head; p != NULL; p = p->next)
	{   
		printf("%d ", p->data);   
	}   
	printf("\n");   
}   
int main(void)
{
	struct node *head = NULL;   
	struct node *p;   
	int i;   
	/**  
	* 初始化链表  
	*/ 
	srand((unsigned)time(NULL));   
	for (i = 1; i < 11; ++i)
	{   
		p = (struct node*)malloc(sizeof(struct node));   
		p->data = rand() % 100 + 1;
		if(head == NULL)
		{
			head = p;
			head->next = NULL;
		}
		else
		{
			p->next = head->next;
			head->next = p;
		}
	}
	print_list(head);
	printf("---------------------------------\n");
	QListSort(&head,NULL);
	print_list(head);
	system("pause");
	return 0;   
}   

void QListSort(struct node **head, struct node *end)
{
	struct node *right;   
	struct node **left_walk, **right_walk;   
	struct node *pivot, *old;   
	int count, left_count, right_count;   
	if (*head == end)
		return;   
	do {   
		pivot = *head;   
		left_walk = head;
		right_walk = &right;   
		left_count = right_count = 0;   
		//取第一个节点作为比较的基准,小于基准的在左面的子链表中,   
		//大于基准的在右边的子链表中   
		for (old = (*head)->next; old != end; old = old->next)
		{   
			if (old->data < pivot->data)
			{      //小于基准,加入到左面的子链表,继续比较   
				++left_count;   
				*left_walk = old;            //把该节点加入到左边的链表中,   
				left_walk = &(old->next);   
			}
			else
			{                         //大于基准,加入到右边的子链表,继续比较   
				++right_count;   
				*right_walk = old;              
				right_walk = &(old->next);   
			}   
		}   
		//合并链表   
		*right_walk = end;       //结束右链表   
		*left_walk = pivot;      //把基准置于正确的位置上   
		pivot->next = right;     //把链表合并   
		//对较小的子链表进行快排序,较大的子链表进行迭代排序。   
		if(left_walk > right_walk)
		{
			QListSort(&(pivot->next), end);   
			end = pivot;   
			count = left_count;   
		}
		else
		{   
			QListSort(head, pivot);   
			head = &(pivot->next);   
			count = right_count;   
		}   
	}
	while (count > 1);    
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值