第十课 — 循环链表,约瑟夫环问题的解决

单链表的局限 

有些线性关系是循环的,即没有队尾元素 

一年12个月,是重复的,12月过了又要回到1月,对于这样的线性元素规律,前人给我指明了一条更好的道路:循环链表。

循环链表拥有单链表的所有操作

创建链表
销毁链表
获取链表长度
清空链表
获取第pos个元素操作
插入元素到位置pos
删除位置pos处的元素

循环链表的新操作

获取当前游标指向的数据元素
将游标重置指向链表中的第一个数据元素
将游标移动指向到链表中的下一个数据元素
直接指定删除链表中的某个数据元素 

CircleListNode* CircleList_DeleteNode(CircleList* list, CircleListNode* node);
CircleListNode* CircleList_Reset(CircleList* list);
CircleListNode* CircleList_Current(CircleList* list);
CircleListNode* CircleList_Next(CircleList* list); 

删除操作和单链表不同,直接删除元素,而不是像单链表那样删除函数指定的是pos位置,但是还是借助于了之前单链表删除位置的操作,具体看下面的代码分析。

循环链表的实现由很多地方和单链表思想一致,只是需要我们根据情况做一些更改。

循环链表头结点:

typedef struct _tag_CircleList
{
    CircleListNode header;
    CircleListNode* slider;//游标
    int length;
} TCircleList;

头文件类型声明:

typedef void CircleList;
typedef struct _tag_CircleListNode
{
    struct _tag_CircleListNode* next;
}CircleListNode;

循环链表的创建:

CircleList* CircleList_Create()
{
    TCircleList * clist=(TCircleList *)malloc(sizeof(TCircleList));
    if(clist != NULL)
    {
        clist->header.next=NULL;
        clist->slider=NULL;
        clist->length=0;
    }
    return clist;
}

上面的代码创建一个循环链表的表头,包含结构,这个结构包含一个指向自身结构类型的指针,和一个游标指针,还有一个长度。

循环链表的销毁,清除,获取长度和单链表的时候一致:

 

void CircleList_Destroy(CircleList* list)
{
    free(list);
}

void CircleList_Clear(CircleList* list)
{
    TCircleList* sList = (TCircleList*)list;
    
    if( sList != NULL )
    {
        sList->length = 0;
        sList->header.next = NULL;
        sList->slider = NULL;
    }
}

int CircleList_Length(CircleList* list) 
{
    TCircleList* sList = (TCircleList*)list;
    int ret = -1;
    
    if( sList != NULL )
    {
        ret = sList->length;
    }
    
    return ret;
}

 

插入节点,这和单链表的思路一样,但是实现需要做一定更改:

int CircleList_Insert(CircleList* list, CircleListNode* node, int pos)
{
    TCircleList *clist=(TCircleList *)list;
    int ret=(list != NULL && node != NULL && pos>=0);// !=优先级大于&&
    int i=0;
    if(ret)
    {
        CircleListNode* current=(CircleListNode*)clist;
        for (i = 0; i < pos && current->next != NULL; ++i)//思考为什么要加current->next != NULL?
        {
            current=current->next;//满足条件进入了说明不是第一次插入,即pos等于0处已经插入过了。
        }
        node->next=current->next;
        current->next=node;
        if(clist->length==0)//第一次插入
        {
            node->next=node;//循环链表,需要把尾部指向头部
            clist->slider=node;//游标要指向第一个节点
        }
        clist->length++;
    }
  return ret;
}

current->next != NULL是保证current指针的移位是正确的,如果current->next  == NULL,那么第一次插入就不会进入这个for循环,而当链表只有头结点的时候,是不应该执行current指针的移位的。

获取pos位置的地址:

CircleListNode* CircleList_Get(CircleList* list, int pos)
{
    TCircleList *clist=(TCircleList *)list;
    int i ;
    if( (clist != NULL) && (pos >= 0) )
    {
        CircleListNode* current = (CircleListNode*)clist;
        
        for(i=0; i<=pos; i++)//移动pos+1次,刚好取得pos位置
        {
            current = current->next;
        }
        return current;
    }
    return NULL;
}

循环链表,获取位置不受链表长度的影响,这和单链表不同,循环链表只用限定pos大于等于0,而单链表应该限定获取的pos要小于长度。如同我们非要说第13个月,那么我们也知道那是第二年的1月。

 删除指定位置:

CircleListNode* CircleList_Delete(CircleList* list, int pos) 
{
    TCircleList * clist=(TCircleList *)list;
    CircleListNode* ret = NULL;
    int i = 0;
    if(clist !=NULL && pos>=0)
    {
        CircleListNode* current = (CircleListNode*)clist;
        //CircleListNode* first = sList->header.next;
        CircleListNode* last = (CircleListNode*)CircleList_Get(clist, clist->length - 1);
        for (i = 0; i < pos; ++i)//正常删除,不是特殊点
        {
            current=current->next;
        }
        ret=current->next;
        current->next=ret->next;
        clist->length--;

        //特殊点,如果删除的是第一个节点
        if (current==(CircleListNode*)clist)
        {
            //clist->header.next = ret->next;
            last->next = ret->next;
        }
        //如果删除的是游标指向的位置,需要把游标后移
        if( clist->slider == ret )
        {
            clist->slider = ret->next;
        }
        //如果上面的删除导致没有节点了,需要把头结点还原
        if( clist->length == 0 )
        {
            clist->header.next = NULL;
            clist->slider = NULL;
        }
        
    }
    return ret;
}

删除指定节点:

CircleListNode* CircleList_DeleteNode(CircleList* list, CircleListNode* node) 
{
    TCircleList * clist=(TCircleList *)list;
    CircleListNode* ret = NULL;
    int i = 0;
    
    if( clist != NULL )
    {
        CircleListNode* current = (CircleListNode*)clist;
        
        for(i=0; i<clist->length; i++)
        {
            if( current->next == node )//如果找到要删除的节点,就把这个节点返回
            {
                ret = node;
                //ret = current->next;和上面的等价
                break;
            }
            
            current = current->next;
        }
        
        if( ret != NULL )//不等于NULL证明上面的for循环找到了对应需要删除的节点
        {
            CircleList_Delete(clist, i);
        }
    }
    
    return ret;
}

上面红色部分,是借用了之前实现的删除节点函数。

游标的复位: 

CircleListNode* CircleList_Reset(CircleList* list)
{
    TCircleList* slist = (TCircleList*)list;
    CircleListNode* ret = NULL;
    if (slist != NULL)
    {
        slist->slider=slist->header.next;
        ret=slist->slider;
    }
    return ret;
}

复位比较简单,就是回到第一个节点。

获取当前游标的信息: 

CircleListNode* CircleList_Current(CircleList* list)
{
    TCircleList *clist=(TCircleList *)list;
    CircleListNode* ret=NULL;
    if (clist !=NULL)
    {
        ret=clist->slider;
    }
    return ret;
}

移动游标至下一个位置:

CircleListNode* CircleList_Next(CircleList* list)
{
    TCircleList *clist=(TCircleList *)list;
    CircleListNode* ret=NULL;
    if (clist != NULL && clist->slider !=NULL)
    {
        //clist->slider=clist->header.next;
        ret = clist->slider;
        clist->slider = ret->next;
    }
    return ret;
}

定义辅助指针变量ret,先保存游标的值,然后ret后移一位,赋值给游标,这样就实现了游标的移动,返回移动前的游标。为什么ret->next 就可以相当于移动游标?ret = clist->slider;游标赋值给ret,此时ret是指向第一个节点的,因为游标在有元素插入之后是指向第一个节点的。ret->next就是下一个位置的地址,这样再把clist->slider = ret->next;就相当于把此时位置的后面一个位置的地址给了游标了,就达到了游标的移位,同时返回游标移位之前的位置,这样我们就可以通过这个游标的返回值访问用户定义的数据。

代码练兵场:

约瑟夫环问题

个人围成一个圆圈,首先第 个人从 开始一个人一个人顺时针报数,报到第 个人,令其出列。然后再从下一 个人开始从 顺时针报数,报到第 个人,再令其出列,,如此下去,求出列顺序 。

这一类题目在面试中经常遇见,今天我们就使用循环链表来将其解决。

main.c 

#include <stdio.h>
#include <stdlib.h>
#include "CircleList.h"

struct Value
{
    CircleListNode header;
    int v;
};

int main(int argc, char *argv[])
{
    int i = 0;
    CircleList* list = CircleList_Create();
    
    struct Value v1;
    struct Value v2;
    struct Value v3;
    struct Value v4;
    struct Value v5;
    struct Value v6;
    struct Value v7;
    struct Value v8;
    
    v1.v = 1;
    v2.v = 2;
    v3.v = 3;
    v4.v = 4;
    v5.v = 5;
    v6.v = 6;
    v7.v = 7;
    v8.v = 8;
    
    CircleList_Insert(list, (CircleListNode*)&v1, CircleList_Length(list));
    CircleList_Insert(list, (CircleListNode*)&v2, CircleList_Length(list));
    CircleList_Insert(list, (CircleListNode*)&v3, CircleList_Length(list));
    CircleList_Insert(list, (CircleListNode*)&v4, CircleList_Length(list));
    CircleList_Insert(list, (CircleListNode*)&v5, CircleList_Length(list));
    CircleList_Insert(list, (CircleListNode*)&v6, CircleList_Length(list));
    CircleList_Insert(list, (CircleListNode*)&v7, CircleList_Length(list));
    CircleList_Insert(list, (CircleListNode*)&v8, CircleList_Length(list));
    
    for(i=0; i<CircleList_Length(list); i++)
    {
        struct Value* pv = (struct Value*)CircleList_Next(list);
        
        printf("%d\n", pv->v);
    }
    
    printf("\n");
    
    CircleList_Reset(list);
    
    while( CircleList_Length(list) > 0 )
    {
        struct Value* pv = NULL;
        
        for(i=1; i<3; i++)//我们游标只用移动两次
        {
            CircleList_Next(list);
        }
        
        pv = (struct Value*)CircleList_Current(list);
        
        printf("%d\n", pv->v);
        
        CircleList_DeleteNode(list, (CircleListNode*)pv);
    }
    
    CircleList_Destroy(list);
    
    return 0;
}

 

 先打印1到8,然后约瑟夫环问题输出,结果和上面的图片一致。

 

循环链表比单链表更加灵活。

用循环链表这样的数据结构解决了约瑟夫环问题,使用数学公式推导的解决办法肯定是最佳的,但是数学推导又有几个人能那么容易得到呢?循环链表可以很好的解决,但是实现一个循环链表,也是颇费时间的,但是这次我们写好了之后,以后就可以复用了。

数学是每个程序员的必修的,然而大部分人都在数学能力上逐步下滑,这样是成为不了优秀的程序员的。

 

posted @ 2017-08-07 21:07  Liu_Jing  Views(661)  Comments(0Edit  收藏  举报