数据结构与之线性表

线性表

定义:零个或多个数据元素的有限序列。
若有多个元素,则第一个元素无前驱,最后一个元素无后继。其他每个元素都有且只有一个前驱和后继。

1.抽象数据类型

2.顺序存储实现

线性表对一个数组进行封装
代码描述:

#define MAXSIZE 20
typedef int ElemType;
typedef struct
{
    /* data */
    ElemType data[MAXSIZE];
    int length;
}Sqlist;

顺序存储结构封装的三个属性:存储空间的起始位置、线性表的最大存储容量、线性表当前长度。
地址计算方法:Loc(ai) = Loc(a1) + (i -1)*c 注:c表示ElemType的存储单元(字节)。

获得元素

思路:将线性表L中的第i个位置元素值返回。

//1.获得元素操作
/*
Status是函数的类型,其值是函数结果的状态代码,如OK等
初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
操作结果:用e返回L中第i个数据元素的值
*/
Status GetElem(Sqlist L,int i,ElemType *e){
    if(L.length == 0 || i < 1 || i > L.length){
        return ERROR;
    }
    *e = L.data[i - 1];
    return OK;
}
插入元素

思路: 如果插入位置不合理,抛出异常
如果线性表的长度大于等于数组长度,抛出异常异常或动态 增加容量。
从最后一个元素开始向前表里到第i个位置,分别将他们都向后移动一个位置。
将要插入的元素填入位置i处
表长加1.

//2.插入操作
/*
初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
操作结果:在L中第i个位置之前插入新的数据元素,L的长度加1
*/
Status ListInsert(Sqlist *L,int i,ElemType e){
    int k;
    if (L->length == MAXSIZE)/*顺序线性表已经满了*/
    {
        return ERROR;
    }
    if (i < 1 || i > L->length + 1)/*当i不在范围内时*/
    {
        return ERROR;
    }
    if (i <= L->length)/*若插入书籍位置不在表位*/
    {
        for (k = L -> length - 1;k >= i - 1;k--)/*将要插入位置后数据元素向后移一位*/
        {
           L->data[k+1] = L->data[k];
        } 
    }
    L->data[i -1] = e;
    L->length++;
    return OK;
}
删除操作

思路: 如果删除的位置不合理,跑出异常
取出删除元素
从删除元素位置开始遍历到按最后一个元素位置,分别将他们都向前移动一个位置
表长减一。

//3.删除操作

/*
初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
操作结果:删除L中第i个数据元素,并用e返回其值,L的长度减1
*/
Status ListDelete(Sqlist *L,int i,ElemType *e){
    int k;
    if (L->length == 0)/*线性表为空*/
    {
        return ERROR;
    }
    if(i < 0 || i > L->length)/*删除位置不正确*/
    {
        return ERROR;
    }

    *e = L->data[i - 1]; /*记录要删除的元素*/
    if (i < L->length)
    {
        for(k = i; k < L->length;k++)/*简化删除位置后继元素前移*/
        {
            L->data[k -1] = L->data[k];
        }
    }
    L->length--;
    return OK; 
}

总结:线性表的顺序存储结构,在存、读数据时时间复杂度是O(1)
而插入和删除时,时间复杂度都是O(n)

3.链式存储实现

用一组任意的存储单元存储线性表的数据元素
链式存储结构中每一个结点除了存储数据元素信息外,还要存储它的后继元素的存储地址。
存储数据元素信息的域叫数据域
存储直接后继位置的域叫指针域,存储的信息叫指针或链
每个结点自包含一个指针域的叫作单链表
头指针:链表中的第一个结点的存储位置
头结点:单链表的第一个结点前附设一个结点,用于存储的附加信息。

代码描述:

/*线性表单链表存储结构*/
typedef int ElemType; 
typedef struct Node
{
    /* data */
    ElemType data;
    struct Node *next;
    
} Node;

typedef struct Node * LinkList;/*定义LinkList,将struct Node *重命名为LinkList*/
单链表读取

思路:
1.声明一个结点p指向链表的第一个结点,初始化j从1开始。
2.当j<i时,就是遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;
3.若到链表末尾p为空,则说明第i个元素不存在,
4.否则查找成功,返回结点p的数据

时间复杂度O(n)

//1.获取元素
/*
初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
操作结果:用e返回L中第i个数据元素的值
*/
Status GetElem(LinkList L,int i,ElemType *e){
    int j;
    LinkList p;/*声明一节点p*/
    p = L->next;/*让p指向链表L的第一个节点*/
    j = 1; /*j为计数器*/
    while (p && j < i)/*p不为空或者计数器还没有等于i,循环继续*/
    {
         p = p->next;/*让结点指向下一个节点*/
         ++j;
    }
    if (!p || j > i)
    {
        return ERROR; /*第i个元素不存在*/
    }
    *e = p->data;/*取第i个元素的数据*/
    return OK;
}
单链表插入

思路:
1.声明一个结点p指向链表的第一个结点,初始化j从1开始。
2.当j<i时,就是遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;
3.若到链表末尾p为空,则说明第i个元素不存在,
4.否则查找成功,在系统中生成一个空结点s
5.将数据元素e赋值给s->data
6.单链表的插入标准语句是s->next = p->next; p->next = s;
7.返回成功
时间复杂度O(1)

//2.插入元素
/*
初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
操作结果:在L中第i个位置之前插入新的数据元素,L的长度加1
*/
Status ListInsert(LinkList *L,int i,ElemType e){
    int j;
    LinkList p,s;
    p = *L;
    j = 1;
    while (p && j < i) /*寻找第i个结点*/
    {
       p = p->next;
       ++j;
    }
    if (!p || j > i)
    {
        return ERROR;/*第i个元素不存在*/
    }
    s = (LinkList)malloc(sizeof(Node));/*生成C结点*/
    s->data = e;
    s->next = p->next;/*将p的后继结点赋值给s的后继*/
    p->next = s;/*将s赋值给p的后继*/
    return OK;

}
单链表删除

思路:
1.声明一个结点p指向链表的第一个结点,初始化j从1开始。
2.当j<i时,就是遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;
3.若到链表末尾p为空,则说明第i个元素不存在,
4.否则查找成功,将欲删除的结点p->next赋值给q
5.单链表的删除标准语句是p->next = q->next;
6.将q结点中的数据赋值给e,作为返回
7.释放q结点
8.返回成功

时间复杂度O(1)

//3.删除操作

/*
初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
操作结果:删除L中第i个数据元素,并用e返回其值,L的长度减1
*/
Status ListDelete(LinkList  *L, int i,ElemType *e){
    int j;
    LinkList p,q;
    p = *L;
    j = 1;
    while (p->next && j < i) /*寻找第i个结点*/
    {
        p = p->next;
        ++j;
    }
    if (!(p->next) || j > i)/*第i个元素不存在*/
    {
       return ERROR;
    }
    q = p->next;
    p->next = q->next;
    *e = q->data;
    free(q);
    return OK;
}
单链表整表创建

思路:
1.声明一个结点p和计数器变量i;
2.初始化一个空链表L;
3.让L的头结点的指针指向null,即建立一个带头结点的单链表
4.循环:
生成一新结点赋值给p;
随机生成一数字赋值给p的数据域p->data
将p插入到头结点与前一新节点之间

代码描述:

//4.创建整单链表
/*随机产生n个元素的值,建立带表头结点的单链表线性表L(头插法)*/
void createListHead(LinkList *L,int n){
    LinkList p;
    int i;
    srand(time(0));/*初始化随机种子*/
    *L = (LinkList)malloc(sizeof(Node));/*返回值为void*型*/
    (*L)->next = NULL;/*先建立一个带头结点的单链表*/
    for ( i = 0; i < n; i++)
    {
       p = (LinkList)malloc(sizeof(Node));/*生成新节点*/
       p->data = rand()%100+1;
       p->next = (*L)->next;
       (*L)->next = p;/*插入到表头*/
    }  
}

/*随机产生n个元素的值,建立带表头结点的单链表线性表L(头插法)*/
 void createListTail(LinkList *L,int n){
     LinkList p,r;
     int i;
     srand(time(0));
     *L = (LinkList)malloc(sizeof(Node));//*L相当于头指针
     r = *L;/*r为指向尾部的节点*/
     for ( i = 0; i < n; i++)
     {
         p = (LinkList)malloc(sizeof(Node));
         p->data = rand()%100+1;
         r->next = p;
         r = p;
     }
     r->next = NULL;
 }
单链表整表删除

思路:
1.声明一结点p和q
2.将第一个结点赋值给p
3.循环:
将下一结点赋值给q
释放p
将q赋值给p

代码描述:

//5.单链表的整表删除
 /*初始条件,顺序线性表L已存在,
 操作结果:将L重置为空表
 */
Status ClearList(LinkList *L){
    LinkList p,q;
    p =(*L)->next;/*p指向第一个结点*/
    while (p)
    {
        q = p->next;
        free(q);
        p = q;
    }
    (*L)->next = NULL;
    return OK;
}
单链表结构与顺序存储结构的优缺点
  • 存储分配方式
    • 顺序存储结构用一段连续的存储单元依次存储线性表的数据元素
    • 单链表采用链式存储结构,用一组任意的存储单元存放线性表的数据元素
  • 时间性能
    • 查找
      顺序存储结构O(1)
      单链表O(n)
    • 插入和删除
      顺序存储结构需要平均移动表长一半的元素,时间为O(n)
      单链表在找出某位置的指针后,插入和删除时间为O(1)
  • 空间性能
    • 顺序需要预分配存储空间,分大了,浪费,分小了,容易溢出
    • 单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制

若需要查找比较多,插入和删除较少,则适合用顺序存储结构,反之适合单链表结构。
若不确定元素个数时,适合采用单链表,反之用顺序存储结构

4.静态链表

用数组描述得链表
对数组的第一个和最后一个元素作特殊元素处理,不存数据。把未被使用的数组元素称为备用链表。
在这里插入图片描述
存储结构:

ypedef int ElemType;
#define MAXSIZE  1000
typedef struct
{
    ElemType data;
    int cur;/*游标(cursor),为0是表示无指向*/
}Component,StaticLinkList[MAXSIZE];

初始化:

/*将一位数组sapce中各分量链成一个被用链表*/
Status InitList(StaticLinkList space){
    int i;
    for(i = 0;i < MAXSIZE - 1;i++){
        space[i].cur = i + 1; 
    }
    space[MAXSIZE - 1].cur = 0;/*目前静态链表为空,最后以个元素的cur为0*/
    return OK;
}
插入操作

思路:
每次进行插入时,可以从备用链表上拿到第一个结点作为待插入的新结点。

代码描述:
数组空间分配函数:

/*若备用空间链表非空,则返回分配节点的下标,否则返回0*/
int Malloc_SLL(StaticLinkList space){
    int i = space[0].cur;/*当前数组的第一个元素的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 = MAXSIZE - 1;/*注意k首先是最后一个元素的下标*/
    if(i < 1 || i > ListLength(L) + 1){
            return ERROR;
    }
    j = Malloc_SLL(L);/*获得空闲分量的下标s*/
    if(j){
        L[j].data = e;/*将数值赋值给次分量的data*/
        for ( l = 1; l <= i - 1; l++)
        {
            k = L[k].cur;/*找到第i个元素之前的位置*/
        }
        L[j].cur = L[k].cur;/*把第i个元素之前的cur赋值给新元素的cur*/
        L[k].cur = j;/*把新元素的下标赋值给第i个元素之前的cur*/
        return OK;
    }
    return ERROR;
}
删除操作

思路:

代码描述:
数组空间释放函数:

/*将下标为k的空闲节点回收到备用链表*/
void Free_SSL(StaticLinkList space,int k){
    space[k].cur = space[0].cur;/*把第一个元素cur值赋值给要删除的元素*/
    space[0].cur = k;/*把要删除的分量下标赋值给第一个元素cur*/
}

删除操作

/*删除在L中第i个数据元素e*/
Status ListDele(StaticLinkList L,int i){
    int j,k;
    if(i < 1 || i > ListLength(L)){
        return ERROR;
    }
    k = MAXSIZE - 1;
    for ( j = 1; j <= i - 1; j++)
    {
        k = L[k].cur;
    }
    j = L[k].cur;
    L[k].cur = L[j].cur;
    Free_SSL(L,j);
    return OK;
}

链表长

/*初始条件:静态链表L已存在,操作结果:返回L中数据元素个数*/
int ListLength(StaticLinkList L){
    int j = 0;
    int i = L[MAXSIZE - 1].cur;//指向第一个元素下标
    while (i)
    {
        i = L[i].cur;
        j++;
    }
    return j;
}
优缺点
  • 优点
    在插入和删除时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中插入和删除操作需要移动大量元素的缺点
  • 缺点
    没有解决连续存储分配带来的表长度难以确定的问题
    失去了顺序存储结构的随机存储特性
快速找到位置长度单链表的中间结点

1.普通方法:
首先遍历单链表得到单链表的长度L,再从头结点除法循环L/2次得到单链表的中间节点。
复杂度为:O(L+l/2)=O(3L/2)
2.快慢指针:
设置两个指针search、mid都指向单链表的头结点。其中search的移动速度是mid的两倍,当*search指向末尾节点的时候,mid正好就在中间了。
代码实现:

Status GetMidNode(LinkList L,ElemType *e){
    LinkList search,mid;
    mid = search = L;
    while(search->next->next !=NULL){
        if(search->next->next !=NULL){
            search = search->next->next;
            mid = mid->next;
        }
        else{
            search = search->next;
        }
    }
    *e = mid->data;
    return OK;
}

5.循环链表

将单链表中的终端结点的指针由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表叫单循环链表
原来是判断p->next是否为空,现在是判断p->next不等于头结点,则循环未结束。

两个循环链表合并:

在这里插入图片描述

p = rearA->next;/*保存A表的头结点*/
rearA->next  = rearB->next->next;/*将本是指向B表第一个结点,赋值给rearA->next*/
rearB->next = p;/*将原来A表的头结点赋值给rearB->next*/
free(p);/*释放p*/
约瑟夫问题:

一个圈共有N个人(N为不确定的数字),第一个人的编号为0或者1,假设这边我将第一个人的编号设置为1号,那么第二个人的编号就为2号,第三个人的编号就为3号,第N个人的编号就为N号,现在提供一个数字M,第一个人开始从1报数,第二个人报的数就是2,依次类推,报到M这个数字的人出局,紧接着从出局的这个人的下一个人重新开始从1报数,和上面过程类似,报到M的人出局,直到N个人全部出局,请问,这个出局的顺序是什么?

代码实现(循环链表):

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

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

//创建循环链表
node *create(int n){
    node *p = NULL,*head;//声明用于遍历的指针结点p和头结点
    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++;
            p->next = s;
            p = s;
        }
        s->next = head->next;
        
    }
    free(head);
    return s->next;
}

//6.遍历链表
void ListDisplay(node *L){
    node *p = L;
    do
    {
        printf("%d->",p->data);
        p = p->next;
    }while (p !=L);
    
}

//main
int main(){
    //初始数据
    int n = 10;
    int m = 2;
    int i;
    //创建链表
    node *p = create(n);
    ListDisplay(p);
    printf("\n");
    node *temp;

    
    //遍历循环链表
    while (p !=p->next)
    {
        //从1开始报数
        for ( i = 1; i < m-1; i++)
        {
            p = p->next;//找到报M前的那个人
        }
        printf("%d->",p->next->data);//报数是M的节点

        temp = p->next;//删除第m个节点
        p->next = temp->next;
        free(temp);

        p= p->next;//从报数是M的节点的下一个节点开始
    }
    printf("\n");
    printf("%d",p->data);

    return 0;
}
约瑟夫问题进阶:

进阶约瑟夫问题: 编号为1~n的n个人按顺序成一圈, 每个人持有一个密码m(密码为正整数),
从第1个人开始报数, 数到m的人出局, 且将他的密码作为新的出局号码,
下1个人重新从1开始报数,持续此过程, 直至最后1人

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define TRUE 1U
#define FALSE 0U

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

//创建n个人循环链表
node *create(int n){
    node *p = NULL,*head;//声明用于遍历的指针结点p和头结点
    head = (node *)malloc(sizeof(node));
    p = head;
    node *s;//中间结点
    srand(time(0));//初始化随机种子

    int i=1;
    if (0 < n)
    {
        while (i<=n)
        {
            s = (node *)malloc(sizeof(node));
            s->data = i++;
            s->password = rand()%4 + 1;//随机生成1-10以内的数据
            p->next = s;
            p = s;
        }
        s->next = head->next;
        
    }
    free(head);
    return s;//返回循环链表的最后一个节点, 充当头结点,不是第一个结点
}

//.遍历链表
void ListDisplay(node *L){
    node *p = L->next;
    do
    {
        printf("%d->",p->data);
        p = p->next;
    }while (p !=L->next);
    
}
//main
int main(){
    //初始数据
    int n = 10;
    int i;
    srand(time(0));//初始化随机种子
    int m = rand()%4 + 1;//随机生成1-10以内的数据
    int count = 1;
    //创建链表
    node *p = create(n);
    ListDisplay(p);
    printf("\n");
    printf("%d\n",m);
    node *temp;
    
    //遍历循环链表
    while (p !=p->next)
    {
        //从1开始报数
        for ( i = 1; i < m; i++)
        {
            p = p->next;//找到报M前的那个人
        }
        printf("%d,%d->",p->next->data,p->next->password);//报数是M的节点

        temp = p->next;//删除第m个节点
        p->next = temp->next;
        m = temp->password;
        free(temp);
    }
    printf("\n");
    printf("%d",p->data);//最后剩下的那个人

    return 0;
}
判断单链表是否有环:

方法一:使用p、q两个指针,p总是向前走,但q每次都从头开始走,对于每个节点,看p走的步数是否和q一样。若步数不等,出现矛盾,存在环。

//根据步数来判断是否有环
 Status hasLoop1(LinkList L){
     //定义指针结点
     LinkList node1 = L;
     int num1=0;
     while (node1)//node1结点一直往前走,直到为null
     {
         LinkList node2 = L;
         int num2=0;
         while (node2)//node2结点一直往前走,直到为null
         {
             if(node1 == node2){
                 if (num1 == num2)
                 {
                     break;
                 }else
                 {
                     printf("loop is location:%d\n",num2);
                     return TRUE;
                 }   
             }
             node2 = node2->next;//往后移一个结点
                 num2++;//步数自增
         }
         node1 = node1->next;//往后移一个结点
         num1++;//步数自增
     }
     return FALSE;
 }

方法二:使用p、q两个指针,p每次向前走一步,q每次向前走两步,若在某个时候p == q,则存在环。

//根据快慢指针判断是否有环
 Status hasLoop2(LinkList L){
     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;
         }
         if (p == q)
         {
             return TRUE;
         }
         
     }
     return FALSE;
 }
魔术师发牌问题

问题描述:魔术师利用一副牌中的13张黑牌,预先将他们排好后叠放在一起,牌面朝下。对观众说:“我不看牌,只数数就可以猜到每张牌是什么,我大声数数,你们听,不信?现场演示。”魔术师将最上面的那张牌数为1,把他翻过来正好是黑桃A,将黑桃A放在桌子上,第二次数1,2,将第一张牌放在这些牌的下面,将第二张牌翻过来,正好是黑桃2,也将它放在桌子上这样依次进行将13张牌全部翻出,准确无误。
问题:牌的开始顺序是如何安排的?

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

#define OK 1
#define ERROR 0 
#define TRUE 1
#define FALSE 0
#define cardnumber 13
typedef int Status;

/*线性表单链表存储结构*/
typedef int ElemType;
typedef struct Node
{
    /* data */
    ElemType data;
    struct Node *next;
    
} Node;

typedef struct Node *LinkList;

//创建链表并将值都设为0
LinkList createList(){
    LinkList head = NULL;
    LinkList s,r;
    int i;
    r = head;
    for ( i = 0; i < cardnumber; i++)
    {
        s = (LinkList)malloc(sizeof(Node));
        s->data = 0;
        if (head == NULL)
        {
            head = s;
        }else
        { 
            r->next = s;
        }
        r=s;
    }
    r->next=head;
    return head;
}

//计算顺序
void magic(LinkList head){
    LinkList p;
    p = head;

    int count =2;//从2开始
    p->data = 1;//第一张牌是1
    int i;
    while (1)
    {
        for ( i = 0; i < count; i++)
        {
            p = p->next;
            if (p->data !=0)
            {
                p = p->next;
                i--;
            }
        }
        if (p->data == 0)
        {
            p->data = count;
            count++;
            if (count == 14)
            {
                break;
            }  
        }
    }
}
int main(){
    LinkList L;
    //创建链表
    L = createList();
    //计算顺序
    magic(L);
    printf("sequnce is:\n");
    for (int i = 0; i < cardnumber; i++)
    {
        printf("black%d->",L->data);
        L = L->next;
    }
    
    return 0;
}
拉丁矩阵

拉丁方阵是一种n×n的方阵,方阵中恰有n种不同的元素,每种元素恰有n个,并且每种元素在一行和一列中 恰好出现一次。著名数学家和物理学家欧拉使用拉丁字母来作为拉丁方阵里元素的符号,拉丁方阵因此而得名。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
#define TRUE 1
#define FALSE 0
typedef int Status;

/*线性表单链表存储结构*/
typedef int ElemType;
typedef struct Node
{
    /* data */
    ElemType data;
    struct Node *next;
    
} Node;

typedef struct Node *LinkList;

//创建循环链表
LinkList createList(int n){
    LinkList head,s,r;
    head = (LinkList)malloc(sizeof(Node));

    r = head;

    for (int i = 1; i <= n; i++)
    {
        s = (LinkList)malloc(sizeof(Node));
        s->data = i;
        r->next = s;
        r = s;
    }
    r->next = head->next;
    free(head);
    return r;
}

//6.遍历链表
void ListDisplay(LinkList L){
    LinkList p = L->next;
    do
    {
        printf("%d  ",p->data);
        p = p->next;
    }while (p !=L->next);
    printf("\n");
}
//单链表打印拉丁矩阵
void printLatin(LinkList L,int n){
    LinkList p;
    int j = 1;//控制链表的第几个元素开始
    int i;//控制打印第几行
    for (i = 1; i <= n; i++)
    {
        if (i == 1)
        {
            p = L;
        }else if (j<i)
        {
            p = p->next;
            j++;
        }
        ListDisplay(p); 
    }
}

//数组实现
void latin(){
    int a[100][100];
    int m = 4;
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < m; j++)
        {
            a[i][j] = i+j+1;
            if (a[i][j]>m)
            {
                a[i][j] %=m;
            }   
            printf("%d  ",a[i][j]);
        }
        printf("\n");
    }
    
}
//数组+链表
void latinList1(LinkList L){
    LinkList cur1,cur2,p= L->next;
    int a[100][100];
    int n =4;
    int i,j;
    for (i = 0,cur1=p; i < n; i++,cur1=cur1->next)
    {
        for (j = 0,cur2 = cur1; j < n; j++,cur2 = cur2->next)
        {
            a[i][j] = cur2->data;
            printf("%d  ",a[i][j]);
        }
        printf("\n");
    }
}

//数组+链表
void latinList2(LinkList L){
    LinkList curr,p= L->next;
    int a[100][100];
    int n =4;
    int i,j;
    for (i = 0,curr=p; i < n; ++i, curr = curr->next) {
        for (j = 0; j < n; ++j) {
            int start = curr->data + j; // 第i行开始元素的值
            a[i][j] = start > n ? start % n : start;
            printf("%d  ",a[i][j]);
        }
        printf("\n");
    }
}

int main(){

    LinkList L;
    int n = 4;
    L = createList(n);
    ListDisplay(L);
    //1
    printLatin(L,n);
    //2
    latin();
    //3
    latinList1(L);
    latinList2(L);
}

6.双向链表

双向链表就使在单链表的基础上,在每个结点中在设置一个指向其前驱结点的指针域,也就是说双链表的每个节点都有两个指针域,一个指向直接后继一个指向直接前驱。

双链表存储结构

 typedef struct DulNode
{
    /* data */
    ElemType data;
    struct Node *prior;/*直接前驱指针*/
    struct Node *next;/*直接后继指针*/
} DulNode,*DulLinkList;

在一个双向链表中,对于链表中的一个结点p, 它的后继的前驱是它本身,它的前驱的后继也是它自己。即:

p->next->prior = p = p->prior->next

在插入和删除数据时需要修改两个指针变量
插入操作:
在这里插入图片描述

s->prior = p;/*把p赋值给s的前驱,如1*/
s->next = p->next;/*把p->next赋值给s的后继如2*/
p->next->prior = s;/*把s赋值给p->next的前驱如3*/
p->next = s;/*把s赋值给p的后继如4*/

顺序是先搞定s的前驱和后继,再搞定后结点的前驱,最后解决前结点的后继。

删除操作:
在这里插入图片描述

p->prior->next = p->next;/*把p->next赋值给p->prior的后继如1*/
p->next->prior = p->prior;/*把p->prior赋值给p->next的前驱如2*/
free(p);/*释放结点*/
凯撒和维吉尼亚加密算法

凯撒加密算法:

维吉尼亚加密算法:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值