双向循环链表的冒泡排序

一、复习数组的冒泡排序

http://blog.csdn.net/longintchar/article/details/75710000
上面这篇博文我介绍了数组的冒泡排序。

冒泡排序属于蛮力法,它比较表中的相邻元素,如果它们是逆序的话就交换它们的位置。重复多次后,最终,最大的元素就“冒”到列表的最后一个位置。第二遍操作将第二大的元素“冒”出来。这样一直重复,直到n-1遍(假设列表共有n个元素)以后,该列表就排序好了。

示意图如下所示:

这里写图片描述

从上图中的start(最左边)开始,向右两两比较,比较一轮后,最大的数冒到最右边,占据end的位置;
end向左移动一个位置,再从start(最左边)开始,向右两两比较……
三轮过后,4个数就排序OK了。

数组的冒泡排序代码如下:

void bubble_sort(int *arr, int len)
{
    int start;
    int end;

    for (end=len-1; end>0; end--) 
    {
        for (start=0; start<end; ++start) 
        {
            if(arr[start] > arr[start+1])
                swap(arr+start, arr+start+1);       
        }
    }
}

二、复习内核链表

既然是链表的排序,那肯定要有链表。用别人造好的轮子当然是最省时省力的办法,不如我们把Linux内核链表拿来用用吧。

下文要用到的函数如下:

1. 结点的插入

static inline void __list_add(struct list_head *new,
                  struct list_head *prev,
                  struct list_head *next)
{
    next->prev = new;
    new->next = next;
    new->prev = prev;
    prev->next = new;      
}   

__list_add这个函数表示把新结点插入到prev和next之间。

2. 结点的删除

static inline void __list_del(struct list_head * prev, struct list_head * next)
{
    next->prev = prev;
    prev->next = next;     
}


static inline void list_del(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
}

list_del用来删除某个结点。

3.遍历和逆向遍历

#define list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)

#define  list_for_each_reverse(cur, head)   \
    for (cur = (head)->prev; cur != head; cur = (cur)->prev) //内核源码好像没有这个宏,我们可以自己加上

另外,还用到了一些函数,由于经常用,这里就不贴了。源码可以参考我的博文 http://blog.csdn.net/longintchar/article/details/78034827

三、排序完整代码

#include <stdio.h>
#include "list.h" //list.h这个文件需要你自己打造,可以拷贝内核源码,也可以参考我的博文

struct data_info {
    int data;
    struct list_head list;
};

int  cmp_data(struct list_head *a, struct list_head *b)
{
    struct data_info *pa = list_entry(a, struct data_info, list);
    struct data_info *pb = list_entry(b, struct data_info, list);
    return pa->data - pb->data;
}

void  swap(struct list_head *a, struct list_head *b)
{
    struct  list_head flag = {NULL, NULL};
    __list_add(&flag, b->prev, b);
    list_del(b);
    __list_add(b, a->prev, a);
    list_del(a);
    __list_add(a, flag.prev, &flag);
    list_del(&flag);
}

void  bubble_sort(struct list_head *head, 
        int  (*compar)(struct list_head *, 
                struct list_head *))
{
    struct list_head *start = NULL; 
    struct list_head *end = NULL;   
    list_for_each_reverse(end, head) 
    {   
        list_for_each(start, head) 
        {           
            if (start == end)                       
                break;

            if (compar(start, start->next) > 0) 
            {
                swap(start, start->next);                           
                start = start->prev; //start归位
                if (start == end) 
                    end = end->next; //end归位            
            }
        }
    }
}

int main(void)
{
    struct data_info s[] =  {{6}, {4}, {7}, {9}, {2}, {8}, {5}, {1}, {3}};

    LIST_HEAD(head);
    int i;
    for (i = 0; i < sizeof s/ sizeof *s; ++i) 
    {
        list_add_tail(&s[i].list, &head);
    } //尾插,构成链表

    struct data_info *pdata = NULL;
    list_for_each_entry(pdata, &head, list) 
    {
        printf("%d ", pdata->data);
    }
    printf("\n"); //排序之前

    bubble_sort(&head, cmp_data); //进行排序

    list_for_each_entry(pdata, &head, list) 
    {
        printf("%d ", pdata->data);
    }
    printf("\n"); //排序之后
    return 0;
}

运行结果如下:

6 4 7 9 2 8 5 1 3
1 2 3 4 5 6 7 8 9

四、代码解析

1.比较大小是一个函数指针

排序函数的原型是:

void  bubble_sort(struct list_head *head, int  (*compar)(struct list_head *, struct list_head *))

第一个参数是链表的头结点(指针),第二个参数是指向函数的指针,这个函数由用户定义。因为数据类型是用户定义的,所以只有用户才清楚如何比较数据。
本代码中,我们定义的链表元素是整数,比较大小也很简单,直接相减就可以了。

struct data_info {
    int data;
    struct list_head list;
};

int  cmp_data(struct list_head *a, struct list_head *b)
{
    struct data_info *pa = list_entry(a, struct data_info, list);
    struct data_info *pb = list_entry(b, struct data_info, list);
    return pa->data - pb->data;
}

2.交换函数

冒泡排序就是靠一轮轮的比较和交换,比较前文说过了,不是难点,那么如何交换呢?

仔细想想,这个交换还是挺麻烦的。有人说,把a结点的数据域和b结点的数据域交换就可以了。这是一个办法,优点是不用移动结点,单纯拷贝数据域就行;缺点是不够通用,因为你无法预知用户定义的是什么数据。所以,为了通用一些,我们还是要移动结点。

试想,我们先把a结点从链表中删除,然后把a结点插入到b结点的后面,再把b结点删除,最后把b结点插入到a结点原来的位置。这里的问题是,一旦把a结点从链表中删除,a的原位置就丢失了,所以是无法把b结点插入到a结点原来的位置的。

所以,我们要想办法记录a结点的原位置。非常容易想到的办法是——用指针记录下a结点的前驱和后继。于是我写出了以下代码:

void  swap_wrong(struct list_head *a, struct list_head *b)
{
    struct  list_head *prev = a->prev;
    struct  list_head *next = a->next;

    list_del(a);
    __list_add(a, b->prev, b);
    list_del(b);
    __list_add(b, prev, next);
}

乍一看,上面的代码还挺对的,可是仔细一想,考虑还不周全。经过测试,我发现上面的代码只适用于两个结点不相邻的情况,一旦a和b相邻,那么就出错了——无法正确交换,而且使b结点自己指向自己。

如果考虑相邻的情况,上面的代码可以修改为:

void  swap(struct list_head *a, struct list_head *b)
{
    struct  list_head *prev = a->prev;
    struct  list_head *next = a->next;
    if(a->next == b)
    {
        list_del(b);
        __list_add(b, a->prev, a);
    }
    else if(b->next == a)
    {
        list_del(a);
        __list_add(a, b->prev, b);
    }
    else
    {
        list_del(a);
        __list_add(a, b->prev, b);
        list_del(b);
        __list_add(b, prev, next);
    }
}

经过测试,以上代码没有问题。但是,这种写法和第三节的写法还是不一样的,显然三的写法更简洁。

void  swap(struct list_head *a, struct list_head *b)
{
    struct  list_head flag = {NULL, NULL};
    __list_add(&flag, b->prev, b);
    list_del(b);
    __list_add(b, a->prev, a);
    list_del(a);
    __list_add(a, flag.prev, &flag);
    list_del(&flag);
}

这种写法的优点是不用分情况讨论,不管a和b是否相邻,都是适用的。

示意图如下:

这里写图片描述

3.排序函数

void  bubble_sort(struct list_head *head, 
        int  (*compar)(struct list_head *, 
                struct list_head *))
{
    struct list_head *start = NULL; 
    struct list_head *end = NULL;   
    list_for_each_reverse(end, head) 
    {   
        list_for_each(start, head) 
        {           
            if (start == end)                       
                break;

            if (compar(start, start->next) > 0) 
            {
                swap(start, start->next);                           
                start = start->prev; //start归位
                if (start == end) 
                    end = end->next; //end归位            
            }
        }
    }
}

第7行:外层循环,使end结点依次从表尾向首结点取值;
第9行:内层循环,使start结点依次从首结点向表尾取值;
第11~12行:一旦start和end重合,跳出内层循环;
第14~16行:从表头到表尾按照升序排列;
第17~19:这几行非常重要,也非常容易被忽略。为了强调,我放到下节说。

4.指针的归位

在数组排序中,游标是不需要归位的,因为我们交换的不是内存地址,而是内存的内容。但是,在本文的链表排序中,我们交换的是结点的地址,也就是说结点的位置改变了。

举例来说,假设当前start指向第3个结点,之后发生了交换,第3个和第4个交换了,那么随着交换的发生,start指向了第4个结点(原来的3变成了现在的4),如果不修正start,继续迭代,那么start = start->next,即指向第5个结点,从第3到第5显然不对,4去哪里了?

所以,发生交换后,需要把start归位,之前指向第几个结点,现在还要指向第几个。所以有了第17行。

如果start和end交换了,那么还要归位end,道理同上。于是有了18~19行。

【完】

双向链表的双向冒泡排序可以通过比较相邻节点的数据域来实现。具体步骤如下: 1. 首先,定义一个指向链表头节点的指针,命名为`current`。 2. 使用两个循环嵌套,外层循环控制比较的轮数,内层循环控制每一轮的比较次数。 3. 在内层循环中,比较`current`节点和`current.next`节点的数据域,如果前者大于后者,则交换它们的数据域。 4. 内层循环结束后,将`current`指针指向下一个节点,继续进行下一轮的比较。 5. 外层循环结束后,整个链表的数据域将按照从小到大的顺序排列。 下面是一个示例代码,演示了双向链表的双向冒泡排序: ```python class Node: def __init__(self, data): self.data = data self.prev = None self.next = None def bubble_sort(head): if head is None or head.next is None: return head end = None while end != head.next: current = head.next while current.next != end: if current.data > current.next.data: current.data, current.next.data = current.next.data, current.data current = current.next end = current return head # 创建双向链表 head = Node(4) node1 = Node(2) node2 = Node(1) node3 = Node(3) head.next = node1 node1.prev = head node1.next = node2 node2.prev = node1 node2.next = node3 node3.prev = node2 # 执行双向冒泡排序 sorted_head = bubble_sort(head) # 输出排序后的链表数据 current = sorted_head.next while current is not None: print(current.data) current = current.next ``` 输出结果为: ``` 1 2 3 4 ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值