码迷,mamicode.com
首页 > 编程语言 > 详细

链表操作法则之逆向遍历与倒置算法

时间:2016-07-18 20:19:59      阅读:512      评论:0      收藏:0      [点我收藏+]

标签:

一、创建链表:

对链表进行操作的所有算法的前提,就是我们首先要创建一个链表,我们可以选择正向建链和逆向建链:

(一)、正向建链:

首先,我们得自定义节点类型:

typedef struct Node
{
    int data;//数据域
    struct Node * pNext;//指针域
}NODE,*PNODE;

通过数组进行链表数据域的赋值:

int main (void)
{
    PNODE pHead;//头指针,接收创建链表时返回的头结点地址
    int a[8] = {12,37,49,65,24,99,8,11};

    //PNODE = ForwardCreate_Link (a,8);//正向建链
    PNODE = ReverseCreate_Link (a,8);//逆向建链
    Print_Link(pHead);//遍历链表
}

正向建链的基本思想是先创建一个头结点,然后每次往链表的尾部插入一个节点,称为尾插。其代码如下:

/*正向建链*/
PNODE ForwardCreate_Link (int * a, int len)//返回值类型为struct Node *类型
{
    PNODE pHead, pTail;
    pHead = NULL;
    for(int i = 0; i<len; ++i)
    {
        PNODE pNew = (PNODE)malloc(sizeof(NODE));
        if(NULL == pNew)
        {
            printf("内存分配失败!");
            exit(-1);
        }
        pNew->pNext = NULL;
        pNew->data = a[i];

        if(NULL == pHead)
            pTail = pHead = pNew;//如果头指针为空,把新建的节点作为头结点
        else
            pTail = pTail->pNext = pNew;//否则新建节点与原尾节点相连作为新的尾节点,移动pTail
    }
    return pHead;
}

(二)、逆向建链:

由于正向建链存在头结点和非头结点的分类创建,我们采用更好的逆向建链的方法来创建链表,每次在原有链表的头结点前插入一个新的节点,作为新的头结点,最开始链表为空时NULL便是头节点:

/*逆向建链*/
PNODE ReverseCreate_Link(int * a, int len)
{
    PNODE pHead = NULL;
    int i = len-1;

    for(i; i>=0; --i)
    {
        PNODE pNew = (PNODE)malloc(sizeof(NODE));
        if(NULL == pNew)
        {
            printf("分配内存失败");
            exit(-1);
        }
        pNew->data = a[i];
        pNew->pNext = pHead;//新建节点头插到原有头结点前面
        pHead = pNew;//头指针移到新的头结点上
    }

    return pHead;
}

二、逆向遍历与倒置:

(一)、时间复杂度较大的链表逆向遍历算法:

我们先设置一个”标兵指针“pEnd,关于“标兵”的类似用法可参照我的 数组元素奇偶排序程序中的死循环引起的思考
pEnd指向尾节点,再开始从头节点开始找标兵位置,找到后输出pEnd->data,标兵前移,循环以上步骤即可逆向遍历链表。代码如下:

/*时间复杂度较高*/
void BackTravOne_Link(PNODE pHead)
{
    PNODE p;
    PNODE pEnd = NULL;//标兵

    while(pEnd != pHead)
    {
        for(p=pHead; p->pNext != pEnd; p = p->pNext);
        printf("%d\t",p->data);
        pEnd = p;
    }
}

(二)、空间复杂度较大的链表逆向遍历算法:

由于每输出一次pEnd->data,pEnd前面的节点都必须遍历一遍,做了很多无用功浪费了大量时间。我们以前讲过:所有数据集合的移动算法都可以通过浪费空间来解决。若果内存空间足够大又为了节省时间,我们可以借用数组来依次接收链表的数据,再将数组逆向输出,便可解决时间复杂度过大的问题,代码如下:

void BackTravTwo_Link(PNODE pHead)
{
    int Receive[MAX];
    PNODE p = pHead;
    int i = 0;

    for (i,p; p; ++i,p=p->pNext)
        Receive[i] = p->data;
    for(--i; i>=0; --i)
        printf("%d\t",Receive[i]);
}

但是我们还知道,靠浪费空间的方法解决问题并非最优的方法,那么最优的方法又是什么呢??

(三)、链表的逆向遍历与倒置算法:

对于链表的逆向遍历,我们无法确保时间复杂度与空间复杂度都较小,那么如果我们将链表倒置之后正向遍历,再将链表倒置回原链表,只要倒置算法时间复杂度与空间复杂度足够小,只需要调用三次函数即可完成逆向遍历。
那么如何将一个链表倒置过来呢?其实倒置的过程就是删除节点与逆向建链的过程,但是删除仅仅是断开原有前驱后驱关系,并不释放内存,而是将断开的节点作为逆向建链的新节点使用。

1、代码:

/*由于最终倒置完成以后,头结点地址发生变化,所以要返回新的头结点地址。*/
PNODE InversionWell_Link(PNODE pHead)
{
    PNODE q = pHead;
    PNODE p = q->pNext;
    while(p)//当p指向NULL时,表明倒置完成,退出循环
    {
        q->pNext = p->pNext;
        p->pNext = pHead;
        pHead = p;
        p = q->pNext;
    }

    return pHead;
}

用倒置法逆向遍历原链表,既没有增加新的大内存,时间复杂度也最小。在链表结点个数成千上万时,其优点尤为突出。
2、图解:

倒置前与倒置后(假设该链表有四个节点):

技术分享

一个完整的节点操作流程(绿线为该步建立的链接,红线为该步断开的链接):

技术分享
技术分享

三、测试完整代码:

# include<stdio.h>
# include <stdlib.h>
# define MAX 100

typedef struct Node
{
    int data;//数据域
    struct Node * pNext;//指针域
}NODE,*PNODE;

PNODE ForwardCreate_Link (int * a, int len);//正向建链
PNODE ReverseCreate_Link(int * a, int len);//逆向建链
void Print_Link(PNODE pHead);//正向遍历
void BackTravOne_Link(PNODE pHead);//借助标兵,逆向遍历
void BackTravTwo_Link(PNODE pHead);//借助数组,逆向遍历
PNODE InversionWell_Link(PNODE pHead);//通过倒置遍历


int main (void)
{
    PNODE pHead;//头指针,接收创建链表时返回的头结点地址
    int a[8] = {12,37,49,65,24,99,8,11};

    printf("创建成功的链表为:\n");
    //pHead = ForwardCreate_Link (a,8);
    pHead = ReverseCreate_Link (a,8);
    Print_Link(pHead);
    printf("\n");

    printf("利用“标兵”遍历:\n");
    BackTravOne_Link(pHead);
    printf("\n");

    printf("利用数组遍历\n");
    BackTravTwo_Link(pHead);
    printf("\n");

    printf("利用倒置链表进行链表遍历:\n");
    pHead = InversionWell_Link(pHead);
    Print_Link(pHead);
    printf("再调用一次InversionWell_Link()函数,倒置回原链表:\n");
    pHead = InversionWell_Link(pHead);
    Print_Link(pHead);
    printf("\n");
    return 0;
}

/*正向建链*/
PNODE ForwardCreate_Link (int * a, int len)//返回值类型为struct Node *类型
{
    PNODE pHead, pTail;
    pHead = NULL;
    for(int i = 0; i<len; ++i)
    {
        PNODE pNew = (PNODE)malloc(sizeof(NODE));
        if(NULL == pNew)
        {
            printf("内存分配失败!");
            exit(-1);
        }
        pNew->pNext = NULL;
        pNew->data = a[i];

        if(NULL == pHead)
            pTail = pHead = pNew;//如果头指针为空,把新建的节点作为头结点
        else
            pTail = pTail->pNext = pNew;//否则新建节点与原尾节点相连作为新的尾节点,移动pTail
    }
    return pHead;
}

/*逆向建链*/
PNODE ReverseCreate_Link(int * a, int len)
{
    PNODE pHead = NULL;
    int i = len-1;

    for(i; i>=0; --i)
    {
        PNODE pNew = (PNODE)malloc(sizeof(NODE));
        if(NULL == pNew)
        {
            printf("分配内存失败");
            exit(-1);
        }
        pNew->data = a[i];
        pNew->pNext = pHead;//新建节点头插到原有头结点前面
        pHead = pNew;//头指针移到新的头结点上
    }

    return pHead;
}

void Print_Link(PNODE pHead)
{
    if(pHead == NULL)
        printf("链表为空!");

    for(PNODE p = pHead; p; p = p->pNext)
        printf("%d\t",p->data);
    printf("\n");
}

void BackTravOne_Link(PNODE pHead)
{
    PNODE p;
    PNODE pEnd = NULL;//标兵

    while(pEnd != pHead)
    {
        for(p=pHead; p->pNext != pEnd; p = p->pNext);
        printf("%d\t",p->data);
        pEnd = p;
    }
    printf("\n");

}

void BackTravTwo_Link(PNODE pHead)
{
    int Receive[MAX];
    PNODE p = pHead;
    int i = 0;

    for (i,p; p; ++i,p=p->pNext)
        Receive[i] = p->data;
    for(--i; i>=0; --i)
        printf("%d\t",Receive[i]);
    printf("\n");
}

PNODE InversionWell_Link(PNODE pHead)
{
    PNODE q = pHead;
    PNODE p = q->pNext;
    while(p)//当p指向NULL时,表明倒置完成,退出循环
    {
        q->pNext = p->pNext;
        p->pNext = pHead;
        pHead = p;
        p = q->pNext;
    }

    return pHead;
}

链表操作法则之逆向遍历与倒置算法

标签:

原文地址:http://blog.csdn.net/apollon_krj/article/details/51939136

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!