单链表逆序2.0(重新整理)

单链表逆序

背景

“单链表逆序”问题。很多公司的面试题库中都有这道题,有的公司明确题目要求不能使用额外的节点存储空间,有的没有明确说明,但是如果面试者使用了额外的节点存储空间做中转,会得到一个比较低的分数。如何在不使用额外存储节点的情况下使一个单链表的所有节点逆序?

一.单链表基本节点

typedef int DataType;

typedef struct node
{
    DataType data;
    struct node *next;
}LinkNode;

基本链表操作先看这篇文章,然后继续往下看

二.逆序实现的几种方式

1.1 head->A->B->C->NULL ==> NULL<-A<-B<-C<-head

思路:1.A变为最后一个节点,A->next = NULL; 然后head->B->A 这种思路

//head->A_node->B_node->C_node->NULL
int reverse_linklist_Debug(LinkNode *head)
{
    LinkNode *cur_node = head;
    LinkNode *next_node = NULL;
    //A_node B_node exist
    if(cur_node->next == NULL || cur_node->next->next == NULL)
    {
        return -1;
    }
    
    next_node = cur_node->next->next; //next_node = head->A_node->B_node
    cur_node->next->next = NULL; //cur_node = head->A_node->NULL
    while(next_node)  //B_node exist
    {
        cur_node = next_node->next;    //cur_node = C_node
        next_node->next = head->next; //B->A
        head->next = next_node;       //Head->B
        next_node = cur_node;         //next_node = cur_node = C_node
    }

    return 0;
}

1.2 head->A->B->C->NULL ==> NULL<-A<-B<-C<-head

但是1.2在1.1上做了优化,pre head next,思路更明确一些,有些不好理解,细细推敲一下。
我们先用迭代循环的思想来分析这个问题,链表的初始状态如图(1)所示:
单链表逆序2.0(重新整理)
图(1)

图(1)初始状态

pre->next = NULL;
Next_Node = head->next;

初始状态,prev是NULL,head指向当前的头节点A,next指向A节点的下一个节点B。首先从A节点开始逆序,将A节点的next指针指向prev,因为prev的当前值是NULL,所以A节点就从链表中脱离出来了,然后移动head和next指针,使它们分别指向B节点和B的下一个节点C(因为当前的next已经指向B节点了,因此修改A节点的next指针不会导致链表丢失)。逆向节点A之后,链表的状态如图(2)所示:
单链表逆序2.0(重新整理)
图(2)

图(2)经过第一次迭代后的状态
从图(1)的初始状态到图(2)状态共做了四个操作,这四个操作的伪代码如下:

head->next = prev;
prev = head;
head = Next_Node ;
Next_Node = head->next;

单链表逆序2.0(重新整理)
图(3)

这四行伪代码就是循环算法的迭代体了,现在用这个迭代体对图(2)的状态再进行一轮迭代,就得到了图(3)的状态.。图(3)经过第二次迭代后的状态,那么循环终止条件呢?现在对图(3)的状态再迭代一次得到图(4)的状态:
单链表逆序2.0(重新整理)
图(4)
此时可以看出,在图(4)的基础上再进行一次迭代就可以完成链表的逆序,因此循环迭代的终止条件就是当前的head指针是NULL。
现在来总结一下,循环的初始条件是:
prev = NULL;
循环迭代体是:

next = head->next;
head->next = prev;
prev = head;
head = next;

循环终止条件是:
head == NULL

根据以上分析结果,逆序单链表的循环算法如下所示:

LinkNode *ReverseLink1(LinkNode *head)
{
    LinkNode *next;
    LinkNode *prev = NULL;

    while(head != NULL)
    {
       next = head->next;
       head->next = prev;
       prev = head;
       head = next;
    }

    return prev;
}

2.递归的思想

现在,我们用递归的思想来分析这个问题。先假设有这样一个函数,可以将以head为头节点的单链表逆序,并返回新的头节点指针,应该是这个样子

LinkNode *ReverseLink2(LinkNode *head)

现在利用ReverseLink2()对问题进行求解,将链表分为当前表头节点和其余节点,递归的思想就是,先将当前的表头节点从链表中拆出来,然后对剩余的节点进行逆序,最后将当前的表头节点连接到新链表的尾部。第一次递归调用ReverseLink2(head->next)函数时的状态如图(5)所示:
单链表逆序2.0(重新整理)
图(5)

图(5)第一次递归状态图
这里边的关键点是头节点head的下一个节点head->next将是逆序后的新链表的尾节点,也就是说,被摘除的头接点head需要被连接到head->next才能完成整个链表的逆序,递归算法的核心就是一下几行代码:

    newHead = ReverseLink2(head->next); /*递归部分*/
    head->next->next = head; /*回朔部分*/
    head->next = NULL;

现在顺着这个思路再进行一次递归,就得到第二次递归的状态图:
单链表逆序2.0(重新整理)
图(6)
第二次递归状态图
再进行一次递归分析,就能清楚地看到递归终止条件了:
单链表逆序2.0(重新整理)
图(7)
第三次递归状态图
递归终止条件就是链表只剩一个节点时直接返回这个节点的指针。可以看出这个算法的核心其实是在回朔部分,递归的目的是遍历到链表的尾节点,然后通过逐级回朔将节点的next指针翻转过来。递归算法的完整代码如下:

//head->A_node->B_node->C_node->NULL
LinkNode *ReverseLink2(LinkNode *head)
{
   LinkNode *newHead;
   if((head == NULL) || (head->next == NULL))
       return head;

    newHead = ReverseLink2(head->next); /*递归部分*/
    head->next->next = head; /*回朔部分*/
    head->next = NULL;
   return newHead;
}

循环还是递归?这是个问题。当面对一个问题的时候,不能一概认为哪种算法好,哪种不好,而是要根据问题的类型和规模作出选择。对于线性数据结构,比较适合用迭代循环方法,而对于树状数据结构,比如二叉树,递归方法则非常简洁优雅。

完整代码

// 单链表
#include <stdio.h>
#include <stdlib.h>

typedef int DataType;

typedef struct node
{
    DataType data;
    struct node *next;
}LinkNode;

LinkNode *create_empty_linklist()
{
    LinkNode *head = NULL;
    head = (LinkNode *)malloc(sizeof(LinkNode));
    head->next = NULL;
    return head;
}


int is_empty_linklist(LinkNode *head)
{
    return head->next == NULL;
}

int insert_head_linklist(LinkNode *head,DataType data)
{
    LinkNode *temp = NULL;
    temp = (LinkNode *)malloc(sizeof(LinkNode));
    temp->data = data;
    temp->next = head->next;
    head->next = temp;
    return 0;
}

int insert_tail_linklist(LinkNode *head,DataType data)
{
    LinkNode *temp = NULL;
    LinkNode *p = head;
    temp = (LinkNode *)malloc(sizeof(LinkNode));
    temp->data = data;
    //找到尾结点
    while(p->next)
    {
        p = p->next;
    }

    //temp->next = NULL;
    temp->next = p->next;
    p->next = temp;
    return 0;
}

int insert_order_linklist(LinkNode *head,DataType data)
{
    LinkNode *temp = NULL;
    LinkNode *p = head;
    temp = (LinkNode *)malloc(sizeof(LinkNode));
    temp->data = data;
#if 0
    while(p->next)
    {
        if(p->next->data > data)
        {
            break;
        }
        p = p->next;
    }
#endif

    /*while(p->next != NULL && p->next->data < data)*/
    while(p->next && p->next->data < data)
    {
        p = p->next;
    }

    temp->next = p->next;
    p->next = temp;
    return 0;
}

int delete_assign_node(LinkNode *head,DataType data)
{

    LinkNode *temp = NULL;
    LinkNode *p = head;
    while(p->next && p->next->data != data)
    {
        p = p->next;
    }

    if(p->next == NULL)
    {
        return -1;
    }

    temp = p->next;
    p->next = p->next->next;
    free(temp);
    return 0;
}

int print_linklist(LinkNode *head)
{
    LinkNode *p = head->next;
    while(p)
    {
        printf("%d ",p->data);
        p = p->next;
    }

    putchar('\n');
    return 0;
}

int print_linklist2(LinkNode *head)
{
    LinkNode *p = head;
    while(p)
    {
        printf("%d ",p->data);
        p = p->next;
    }

    putchar('\n');
    return 0;
}

int reverse_linklist(LinkNode *head)
{
    LinkNode *p = head,*q = NULL;
    if(p->next == NULL || p->next->next == NULL)
    {
        return -1;
    }

    q = p->next->next;
    p->next->next = NULL;
    while(q)
    {
        p = q->next;
        q->next = head->next;
        head->next = q;
        q = p;
    }

    return 0;
}

//head->A_node->B_node->C_node->NULL
int reverse_linklist_1_1(LinkNode *head)
{
    LinkNode *cur_node = head;
    LinkNode *next_node = NULL;
    //A_node B_node exist
    if(cur_node->next == NULL || cur_node->next->next == NULL)
    {
        return -1;
    }


    next_node = cur_node->next->next; //next_node = head->A_node->B_node
    cur_node->next->next = NULL; //cur_node = head->A_node->NULL
    while(next_node)  //B_node exist
    {
        cur_node = next_node->next;    //cur_node = C_node
        next_node->next = head->next; //B->A
        head->next = next_node;       //Head->B
        next_node = cur_node;         //next_node = cur_node = C_node
    }

    return 0;
}

LinkNode *reverse_linklist_1_2(LinkNode *head)
{
    LinkNode *next;
    LinkNode *prev = NULL;

    while(head != NULL)
    {
       next = head->next;
       head->next = prev;
       prev = head;
       head = next;
    }

    return prev;
}

//head->A_node->B_node->C_node->NULL
LinkNode *reverse_linklist_2(LinkNode *head)
{
   LinkNode *newHead;
   if((head == NULL) || (head->next == NULL))
       return head;

    newHead = reverse_linklist_2(head->next); /*递归部分*/
    head->next->next = head; /*回朔部分*/
    head->next = NULL;
   return newHead;
}

int main()
{
    LinkNode *head = NULL;
    int a[] = {1,5,3,4,7,9};
    int i = 0;
    int ret = 0;

    head = create_empty_linklist();
    for(i = 0;i < sizeof(a) / sizeof(a[0]);i++)
    {
        /*insert_head_linklist(head,a[i]);*/
        /*insert_tail_linklist(head,a[i]);*/
        insert_order_linklist(head,a[i]);
    }

//    print_linklist(head);
//    ret = delete_assign_node(head,5);
//    if(ret < 0)
//    {
//        printf("Assign data %d is not exist.\n",5);
//    }

    print_linklist(head);
    //LinkNode *reverse_node = ReverseLink2(head);
   // print_linklist2(reverse_node);

    //ReverseLink1(head);
    LinkNode *reverse_node = reverse_linklist_2(head);
    print_linklist2(reverse_node);
    //print_linklist(reverse_node);

    return 0;
}

打印如下:
单链表逆序2.0(重新整理)
最后一个数是一个小bug,不过那不是重点了。

上一篇:【全程NOIP计划】组合计数选讲


下一篇:队列学习一