数据结构与算法(二)

静态链表

在静态链表中,操作的是数组,不存在像动态链表的结点申请和释放的问题,所以我们需要自己实现这两个函数,才可以做到插入和删除操作。

优点:

  • 在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点。

缺点:

  • 没有解决连续存储分配(数组)带来的表长难以确定的问题。
  • 失去了顺序存储结构随机存取的特性。
  1. 线性表的静态链表存储结构

    #define MAXSIZE 1000
    typedef struct
    {
        ElemType data;	//数据
        int cur;	//游标(cursor)
    } Component, StaticLinkList[MAXSIZE];
    
  2. 对静态链表进行初始化相当于初始化数组

    Status InitList(StaticLinkList space)
    {
        int i;
        for(i=0; i < MAXSIZE-1; i++ )
            space[i].cur = i + 1;
        space[MAXSIZE-1].cur = 0;
        
        return OK;
    }
    
  3. 静态链表的插入操作

    //首先是获得空闲分量的下标
    int Malloc_SLL(StaticLinkList space)
    {
        int i = space[0].cur;
        if( space[0].cur )
            space[0].cur = space[i].cur;
        	//把它的下一个分量用来作为备用
        return i;
    }
    
    
    //在静态链表L中第i个元素之前插入新的数据元素e
    Status ListInsert( StaticLinkList L, int i, ElemType e)
    {
        int j, k, l;
        k = MAX_SIZE - 1; 	//数组的最后一个元素
       	if( i < 1 || i > ListLength(L) + 1)
       	{
           return ERROR;
       	}
       
       	j = Malloc_SLL(L);
       	if( j )
       	{
           L[j].data = e;
           for( l=1; l <= i-1; l++)
           {
               k = L[k].cur;
           }
           L[j].cur = L[k].cur;
           L[k].cur = j;
           
           return OK;
       	}
       	return ERROR;
    }
    
  4. 静态链表的删除操作

    //删除在L中的第i个数据元素
    Status ListDelete(StaticLinkList L, int i)
    {
       int j, k;
    
       if(i < 1 || i > ListLength(L) )
       {
           return ERROR;
       }
    
       k = MAX_SIZE - 1;
    
       for( j =1; j <= i-1; j++ )
       {
           k = L[k].cur;	//k1 = 1, k2 = 5
       }
    
       j = L[k].cur;	//j = 2
       L[k].cur = L[j].cur;
    
       Free_SLL(L, j);
    
       return OK;
    }
    
    //将下标为k的空闲结点回收到备用链表
    void Free_SLL(StaticLinkList space, int k)
    {
       space[k].cur = space[0].cur;
       space[0].cur = k;
    }
    
    //返回L中数据元素个数
    int ListLength(StaticLinkList L)
    {
       int j = 0;
       int i = L[MAXSIZE-1].cur;
    
       while(i)
       {
           i = L[i].cur;
           j++;
       }
    
       return j;
    }
    
  5. 面试题

    /**********************************
    快速找到未知长度单链表的中间节点
    
    方法1:首先遍历一遍单链表以确定单链表的长度L
    	   然后再次从头节点出发循环L/2次找到单链表的中间节点
    	   算法复杂度:O(3L/2)
    	   
    方法2:利用快慢指针:设置两个指针*search *mid都指向单链表的头节点。其中*search的移动速度是*mid的2倍。当*search指向末尾节点的时候,mid就正好在中间了
    **********************************/
    
    //代码实现
    //方法2:
    Status GetMidNode(LinkList L, ElemType *e)
    {
        LinkList search, mid;
        mid = search = L;
        
        while(search -> next != NULL)
        {
            //search移动的速度是mid的2倍
            if(search ->next->next != NULL)
            {
                search = search -> next ->next;
                mid = mid ->next;
            }
            else
            {
                search = search -> next;
            }
        }
        
        *e = mid->data;
        
        return OK;
    }
    

循环链表

将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表成为单循环链表,简称循环链表。

由于终端结点用尾指针rear指示,则查找终端结点是O(1),开始结点是rear->next->next,也是O(1)。

  1. 初始化部分

    /*初始化链表*/
    /***********************************
    一个*代表指针,该指针指向的内存地址就是目标数据,两个*代表二级指针,就是本身是个指针,该指针指向的内存地址存放的也是一个指针,存放的指针指向的内存地址才是目标数据。
    ************************************/
    void ds_init(node **pNode)
    {
        int item;
        node *temp;
        node *target;
        
        printf("输入结点的值,输入0完成初始化\n");
        
        while(1)
        {
            scanf("%d", &item);
            fflush(stdin);
            
            if(item == 0)
                return 0;
            
            if((*pNode) == NULL)
            {
                //循环链表中只有一个结点
                *pNode = (node*)malloc(sizeof(struct CLinkList));
                if(!(*pNode))
                    exit(0);
                (*pNode)->data = item;
                (*pNode)->next = *pNode;
            }
            else
            {
                //找到next指向第一个结点的结点
                for(target = (*pNode); target->next != (*pNode); target = target->next)
                    ;
                
                //生成一个新的结点
                temp = (node *)malloc(sizeof(struct CLinkList));
                
                if(!temp)
                    exit(0);
                
                temp->data = item;
                temp->next = *pNode;
                target->next = temp;
            }
        }
    }
    
  2. 插入链表

    /*链表存储结构的定义*/
    typedef struct CLinkList
    {
        int data;
        struct CLinkList *next;
    }node;
    
    /*插入结点*/
    /*参数:链表的第一个结点,插入的位置*/
    void ds_insert(node **pNode, int i)
    {
        node *temp;
        node *target;
        node *p;
        int item;
        int j = 1;
        
        printf("输入要插入结点的值:");
        scanf("%d", &item);
        
        if(i == 1)
        {//新插入的结点作为第一个结点
            temp = (node *)malloc(sizeof(struct CLinkList));
            
            if(!temp)
                exit(0);
            
            temp->data = item;
            
            //寻找到最后一个结点
            for(target = (*pNode); target->next != (*pNode); target = target->next)
                ;
            
            temp->next = (*pNode);
            target->next = temp;
            *pNode = temp;
        }
        else
        {
            target = *pNode;
            
            for(; j< (i-1); ++j )
            {
                target = target->next;
            }//target指向第二个元素
            
            temp = (node *)malloc(sizeof(struct CLinkList));
            
            if(!temp)
                exit(0);
            
            temp->data = item;
            p = target->next;
            target->next = temp;
            temp->next = p;
        }
    }
    
  3. 删除结点

    /*删除结点*/
    void ds_delete(node **pNode, int i)
    {
        node *target;
        node *temp;
        int j = 1;
        
        if(i == 1)
        {
            //删除的是第一个结点
            //找到最后一个结点
            for(target = *pNode; target->next != *pNode; target = target->next)
                ;
            
            temp = *pNode;
            *pNode = (*pNode)->next;
            target->next = *pNode;
            free(temp);
        }
        else
        {
            target = *pNode;
            
            for(; j< i-1; ++j)
            {
                target = target ->next;
            }
            
            temp = target->next;
            target->next = temp->next;
            free(temp);
        }
    }
    
  4. 搜索结点

    //返回结点所在位置
    int ds_search(node *pNode, int elem)
    {
        node *target;
        int i = 1;
        
        for(target = pNode; target->data != elem && target->next != pNode; ++i)
        {
            target = target->next;
        }
        
        if(target->next == pNode)	//表中不存在该元素
            return 0;
        else
            return i;
    }
    
  5. 遍历

    void ds_traverse(node *pNode)
    {
        node *temp;
        temp = pNode;
        printf("********链表中的元素**********\n");
        
        do
        {
            printf("%d ", temp->data);
        }while((temp = temp->next) != pNode);
        
        printf("\n");
    }
    

约瑟夫问题

//用循环链表模拟约瑟夫问题,把41个人自杀的顺序编号输出

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

typedef struct node
{
    int data;
    struct node *next;
}node;

node *create(int n)
{
    node *p = NULL, *head;
    head = (node*)malloc(sizeof (node));
    p = head;
    node *s;
    int i = 1;
    
    if(0 != n)
    {
        while( i <= n)
        {
            s = (node *)malloc(sizeof (node));
            s->data = i++;		//为循环链表初始化,第一个结点为1,第二个结点为2
            p->next = s;
            p = s;
        }
        s->next = head->next;
    }
    
    free(head);
    
    return s->next;
}

int main()
{
    int n = 41;
    int m = 3;
    int i;
    node *p = create(n);
    node *temp;
    
    m %= n;
    
    while(p != p->next )
    {
        for(i = 1; i < m-1; i++)
        {
            p = p ->next;
        }
        
        printf("%d->", p->next->data );
        temp = p->next;			//删除第m个节点
        p->next = temp->next;
        
        free(temp);
        
        p = p ->next;
    }
    
    printf("%d\n", p->data);
    
    return 0;
}

例题

题目:实现将两个线性表(a1,a2, ...., an)和(b1, b2, ...., bm)连接成一个线性表(a1, ...., an, b1, ......., bm)的运算。

分析:

  • 若在单链表或头指针表示的单循环表上做这种链接操作,都需要遍历第一个链表,找到结点an,然后将结点b1链到an的后面,其执行时间是O(n)。
  • 若在尾指针表示的单循环链表上实现,则只需修改指针,无须遍历,其执行时间是O(1)。
//connect.c
//假设A,B为非空循环链表的尾指针
LinkList Connect(LinkList A, LinkList B)
{
    LinkList p = A->next;		//保存A表的头结点位置
    
    A->next = B->next->next;	//B表的开始结点链接到A表尾
    
    free(B->next);				//释放B表的头结点,初学者容易忘记
    
    B->next = p;
    
    return B;				//返回新循环链表的尾指针
}

题目:判断单链表中是否有环

有环的定义是,链表的尾结点指向了链表中的某个结点

方法:

  • 使用p、q两个指针,p总是向前走,但q每次都从头开始走,对于每个结点,看p走的步数是否和q一样。
  • 快慢指针:使用p、q两个指针,p每次向前走一步,q每次向前走两步,若在某个时候p == q,则存在环
//比较步数的方法
int HasLoop1(LinkList L)
{
    LinkList cur1 = L;	//定义结点cur1
    int pos1 = 0;		//cur1的步数
    
    while(cur1)
    {					//cur1结点存在
        LinkList cur2 = L;	//定义结点cur2
        int pos2 = 0;		//cur2的步数
        while(cur2)
        {					//cur2结点不为空
            if(cur2 == cur1)
            {
                if(pos1 == pos2)	//当cur1与cur2到达相同结点时
                    break;			//走过的步数一样,说明没有环
                else				//否则
                {
                    printf("环的位置在第%d个结点处。\n\n", pos2);
                    return 1;		//有环并返回1
                }
            }
            cur2 = cur2->next;		//如果没有发现环,继续下一个结点
            pos2++;					//cur2步数自增
        }
        cur1 = cur1->next;			//cur1继续向后一个结点
        pos1++;						//cur1步数自增
    }
    return 0;
}
//利用快慢指针的方法
int HasLoop2(LinkList L)
{
    int step1 = 1;
    int step2 = 2;
    LinkList p = L;
    LinkList q = L;
    
    while(p != NULL && q != NULL && q->next != NULL)
    {
        p = p->next;
        if(q->next != NULL)
            q = q->next->next;
        
        printf("p:%d, q:%d \n", p->data, q->data);
        
        if(p == q)
            return 1;
    }
    return 0;
}

魔术师发牌问题

问题描述:

魔术师手中有A、2、3……J、Q、K十三张黑桃扑克牌。在表演魔术前,魔术师已经将他们按照一定的顺序叠放好(有花色的一面朝下)。魔术表演过程为:一开始,魔术师数1,然后把最上面的那张牌翻过来,是黑桃A;然后将其放到桌面上;第二次,魔术师数1、2;将第一张牌放到这些牌的最下面,将第二张牌翻转过来,正好是黑桃2;第三次,魔术师数1、2、3;将第1、2张牌依次放到这些牌的最下面,将第三张牌翻过来正好是黑桃3;……直到将所有的牌都翻出来为止。问原来牌的顺序是如何的。

方法:

  • 用循环链表来解决

    #include <stdio.h>
    #include <stdlib.h>
    
    #define CardNUmber 13
    
    typedef struct node
    {
        int data;
        struct nonde *next;
    }sqlist, *linklist;
    
    linklist CreateLinkList()
    {
        linklist head = NULL;
        linklist s, r;
        int i;
        
        r = head;
        
        for(i = 1; i <= CardNumber; i++)
        {
            s = (linklist)malloc(sizeof(sqlist));
            s->data = 0;
            
            if(head == NULL)
                head = s;
            else
                r->next = s;
            
            r = s;
        }
        
        r->next = head;
        
        return head;
    }
    
    //发牌顺序计算
    void Magician(linklist head)
    {
        linklist p;
        int j;
        int Countnumber = 2;
        
        p = head;
        p->data = 1;	//第一张牌放1
        
        while(1)
        {
            for(j=0; j < Countnumber; j++)
            {
                p = p->next;
                if(p->data != 0)	//该位置有牌的话,则下一个位置
                {
                    p->next;
                    j--;
                }
            }
            
            if(p->data == 0)
            {
                p->data = Countnumber;
                Countnumber ++;
                
                if(Countnumber == 14)
                    break;
            }
        }
    }
    
    //销毁工作
    void DestoryList(linklist* list)
    {
        linklist ptr = *list;
        linklist buff[CardNumber];
        int i = 0;
        
        while(i < CardNumber)
        {
            buff[i++] = ptr;
            ptr = ptr->next;
        }
        
        for(i = 0; i < CardNumber; ++i)
            free(buff[i]);
        
        *list = 0;
    }
    
    int main()
    {
        linklist p;
        int i;
        
        p = CreateLinkList();
        Magician(p);
        
        printf("按如下顺序排列: \n");
        for (i=0;i < CardNumber; i++)
        {
            printf("黑桃%d ", p->data);
            p = p->next;
        }
        
        DestoryList(&p);
        
        return 0;
    }
    
    

拉丁方阵问题

  • 拉丁方阵是一种n*n方阵,方阵中恰有n种不同的元素,每种元素恰有n个,并且每种元素在一行和一列中恰好出现一次。
#include <stdio.h>
#include <stdlib.h>

typedef struct node
{
    int data;
    struct node *next;
}sqlist, *linklsit;


//循环链表的生成
linklsit CreateLinkList(int Number)
{
    linklsit head = NULL;
    linklsit s,r;
    int i;

    r=head;

    for(i=1;i<=Number; i++)
    {
        s=(linklsit)malloc(sizeof(sqlist));
        s->data=i;

        if(head==NULL)
            head=s;
        else
            r->next=s;
        r=s;             //保持r在尾端
    }

    r->next=head;

    return head;
}

int main()
{
    linklsit p,r;
    int number,i,j;

    printf("请输入Latin矩阵的宽度: ");
    scanf("%d", &number);
    printf("\n %d维的Latin矩阵为:\n\n", number);

    p=CreateLinkList(number);
    r=p;

    for(i=0;i<number; i++)
    {
        for(j=0; j<number; j++)
        {
            printf("%3d", r->data);
            r=r->next;
        }
        printf("\n");
        p=p->next;
        r=p;
    }
    printf("\n");

    return 0;
}

posted @ 2021-02-18 16:13  zonkidd  阅读(53)  评论(0编辑  收藏  举报