详解单链表冒泡排序

 

需求

对一个单链表进行升序排序,尽量节省内存,提高速度

思路

不遍历列表获取链表长度,防止列表长度过长影响速度;

不移动每个结点中数据域的内容,防止因为数据量大影响速度;

尽量少创建节点变量,省内存;

解决方案

算法:

使用冒泡排序算法进行排序;

冒泡排序原理,动图演示:

详解单链表冒泡排序_第1张图片

(动图转自网络,如有侵权请通知,我会第一时间删除)

循环分析:

内循环:

假设链表头结点为Head, Head节点内数据域无有效数据,地址域中保存了A的地址;

第一个含有有效数据的结点为A,往后按照 B C D……排序

内循环每次都从排序在第一的有效结点开始比较;

详解单链表冒泡排序_第2张图片

第一次应当比较A和B,如果A大于B互换位置,那么Head的地址域就应该指向了B,B的地址域指向A,A的地址域指向C

详解单链表冒泡排序_第3张图片

此时,前两个结点已经完成了比较,并且将较大的一个放在了第二位,那么就要比较这个较大的和第三个结点的大小

详解单链表冒泡排序_第4张图片

所以我们需要一个指针来定位做比较的两个结点,我把这个指针叫做  pBefore 它的初始值指向Head结点,比较完成后将向后移动一个结点。如上图,第一次比较完成后 pBefore 将指向B结点

以此类推,在最后一次比较前

详解单链表冒泡排序_第5张图片

最后一次比较后

详解单链表冒泡排序_第6张图片

这个时候Z一定是数据最大的一个结点。同时,第二个要比较的结点为NULL,所以,可以用第二个需要比较的结点地址是否为NULL来判断内循环是否结束。

当第二次循环开始后,在比较到第二个需要比较的结点为Z时,就不需要再比较了,因为Z一定是最大的,Y一定比Z小。

所以我们就需要一个指针,来定位内循环的结束,我把这个指针叫做  pCure ,初始值设置为NULL,第一次比较完成后将向前移动一位,所以当第二个要比较的结点与pCure指向的相同时,内循环结束(终止条件)

外循环

每次内循环都要从第一个有效结点开始,所以每次外循环都要初始化pBefore;

每次内循环结束时,都要更新pCure,用来让下一次内循环在适当的位置结束;

最后一次内循环一定比较的是第一和第二个节点的大小,当它结束时,pCure一定指向了第一个结点,外循环结束(终止条件)

代码与代码解释

代码

结点的定义

typedef struct Node
{
    int data;//数据域
    struct Node* pNext;//指针域
}NODE,*PNODE;//NODE等价于struct Node类型,*PNODE等价于struct Node*类型

排序函数

void sort_list(PNODE pHead)//形参为头指针,既指向头结点的指针
{
    if (pHead->pNext == NULL)//当头结点地址域为空时,代表链表为空
    {
        printf("这个链表为空,不能排序");
        return;
    }
/*======》  以下请看代码详解 《======*/

    PNODE pt, pCure, pBefore;
    pCure = NULL;

    while (pHead->pNext != pCure)
    {
        pBefore = pHead;
        while (pBefore->pNext->pNext != pCure )
        {
            if (pBefore->pNext->data > pBefore->pNext->pNext->data)
            {
                pt = pBefore->pNext->pNext->pNext;
                pBefore->pNext->pNext->pNext = pBefore->pNext;

                pBefore->pNext = pBefore->pNext->pNext;
                pBefore->pNext->pNext->pNext = pt;
            }
            pBefore = pBefore->pNext;
        }
        pCure = pBefore->pNext;
    }
    return;
}

 

 

代码解释

    PNODE pt, pCure, pBefore;
    pCure = NULL;

    设置了3个指针 pt 将用来存放临时地址,pCure 将存放指向结束的结点的地址,pBefore 将存放需要比较的两个节点之前的一个节点的地址

        pBefore = pHead;

内循环初始化pBefore指向Head

 

内循环详解

详解单链表冒泡排序_第7张图片

以此图为例:

头指针为pHead,存放的是头结点的地址;

头结点Head的地址域存放的数据为A结点的地址pNext,用头指针表示为 pHead->pNext;

pHead->pNext就是A的地址,pHead->pNext->data 为A存储的数据,pHead->pNext->pNext为A的地址域,存放的是B的地址

pHead->pNext->pNext就是B的地址,pHead->pNext->pNext->data 为B存储的数据,pHead->pNext->pNext->pNext为B的地址域,存放的是C的地址

pHead->pNext->pNext->pNext 就是C的地址

 

详解单链表冒泡排序_第8张图片

比较后,需要调换A,B的位置,具体操作分下列步骤

  1. 在循环中,pBefore表示需要比较的两个节点之前一个结点,所以在第一次比较时 pBefore == pHead;
  2. 将B的地址域(C的地址)pHead->pNext->pNext->pNext 放入临时指针pt,代码: pt = pBefore->pNext->pNext->pNext;
  3. 将B指向A,就是将原B的地址域pHead->pNext->pNext->pNext 存储的C的地址,变为A的地址pHead->pNext,此时,A的地址域依然存有B的地址,可以通过A找到B,也可以通过B找到A,代码:pBefore->pNext->pNext->pNext = pBefore->pNext;
  4. 将Head指向B,就是将Head地址域存储的信息pNext变为B的地址pHead->pNext->pNext,此时,已经不能在通过Head的地址域找到A了,因为pHead->pNext已经存储的是B的地址,这步以后,只能通过B找到A,代码:pBefore->pNext = pBefore->pNext->pNext;
  5. 将A的地址域,改存入在PT中存放的C的地址,注意此时Head的地址域中已经没有A的地址,Head的地址域中存放的是B的地址,B的地址域中存放的是A的地址,所以, pHead->pNext->pNext才是A的地址,pHead->pNext->pNext->pNext才是A的地址域,代码:pBefore->pNext->pNext->pNext = pt;

至此,换位工作完成,当前节点顺序为

此时,将开始比较A和C,那么pBefore应该指向B,pBefore之前指向H,那么pBefore->pNext就指向B,代码: pBefore = pBefore->pNext;

至此,一次循环结束,进入下一次循环

当循环运行,判断完最后两个节点,如下图

pBefore将指向Y,Z的地址域pBefore->pNext->pNext为NULL,与pCure相等,内循环判断语句 while (pBefore->pNext->pNext != pCure )为假,内循环结束

pCure更新为Z的地址,pBefore->pNext;

继续外循环

最后,随着pCure前移,最后一次内循环必然比较的是最前面两个节点,比较完成后,pCure将被更新为第一节点的地址。此时外循环判断语句    while (pHead->pNext != pCure)为假,外循环也结束了

至此,整个链表排序完成!

 

 

 

你可能感兴趣的:(详解单链表冒泡排序)