数据结构
----------------------------------------------
数据结构定义:数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及它们之间的关系和操作等相关问题的学科
译:程序设计=数据结构+算法,数据结构研究的是关系,元素之间存才的一种或多种特定关系的集合
逻辑结构:数据对象中数据元素之间的相互关系
集合结构:数据元素除了同属于一个集合外,没有其余任何关系
线性结构:数据元素之间有一对一的关系
树形结构:数据元素之间存在层次关系。一对多
图形结构:数据元素之间有多对多关系
物理结构:数据元素在计算机中的存储形式
顺序结构:数据元素存放在连续的地址存储单元中,其数据间的逻辑关系与物理关系一致
链式结构:数据元素存放在存储器的任意位置,地址单元任意,前一个元素的存储单元中存储对应元素的数据以及下一个元素的地址
算法
----------------------------------------------
算法定义:算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,每条指令表示成一个或多个操作
译:实现功能的技巧和方式
算法特征:1.输入:0到多个输入
2.输出:至少一个输出
3.有穷性:执行有限的步骤后结束循环,程序执行在可接受的时间范围
4.确定性:每工日步骤有确定的含义,在一定条件下只有一条执行路径,相同的输入只能有一个输出
5.可行性:算法的每一步都可执行,符合其意义,在所指定的环境中可实现
算法设计要求:
1.正确性:符合需求,给出正确答案。无语法错误;合法输入产生符合需求输出;非法输入产生满足规格的说明(错误提示);杂难输入也有对应输出;
2.可读性:注释
3.健壮性:输入数据不合法时,算法做出相关处理,而不是产生异常、崩溃等
4.时间效率高和存储量低:代码质量
算法效率的度量
----------------------------------------------
衡量因素:时间复杂度和空间度复杂度
算法效率==程序执行时间
事后统计方法
通过设计好的程序和数据,利用 计算机计时器对不同算法编制的程序的运行时间进行比较,确定算法效率的高低
缺陷:(1)依据事先编写好的测试程序。花费时间和经历
(2)计算机硬件影响程序的运行速度
事前分析估算方法:在计算机程序编写前。利用统计方法对算法进行估算
影响因素:(1)算法采用的策略、方案
(2)编译产生的代码质量
(3)问题的输入规模
(4)机器执行指令的速度
判断一个算法的效率时。函数中的常数和其他次要项常常可以忽略,主要应该关注主项的阶数
测试算法的效率时需要通过大量数据测试算法效率
时间复杂度:在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。
算法的时间复杂度也是算法时间的度量,记作T(n)=O(f(n)),它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,
称作算法的渐进时间复杂度,简称时间复杂度。其中f(n)是问题规模n的某个函数。以简称为大O记法
本质上计算时间复杂度==执行次数。
一般情况下,随着输入规模增大的情况下,T(n)增长最慢的算法为最优算法。
大O阶计算方法:
1.用常数1取代运行时间中的所有加法常数
2.在修改后的运行次数函数中,只保留最高项
3.如果最高项存在且阶数不为1,则除去与该项相乘的常数
常用时间复杂度所耗费的时间从小到大依次是:O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n)
最坏情况和平均情况
平均运行时间是期望的运行时间
最坏运行时间是一种保障,在应用中,是一种重要的需求,通常除非特别指定,一般提到的运行时间都是最坏情况的运行时间
空间复杂度:通过计算算法所需的存储空间实现,算法的空间复杂度的计算公式记作S(n)=O(f(n)),其中n为问题的规模,f(n)为语句关于n所占用存储空间的函数
通常情况下,时间复杂度指运行时间需求,空间复杂度值运行空间需求。当直接要求求复杂度时,指的是时间复杂度。算法一般追求时间复杂度。
线性表
----------------------------------------------
定义:(List)由0个或多个数据元素组成的有限序列,0个元素时为空表
1.序列:有序的队列
2.若元素存在多个,则第一个元素没有前驱,最后一个元素没有后继,其余元素均有前驱和后继
3.线性表是有限的
数学语言:若将线性表记为a1……ai-1、ai、ai+1……an,则ai-1是ai的直接前驱元素,ai是ai+1的直接后继元素
数据类型:指一组性质相同的值的集合及定义在此集合上的一些操作的总称。
分为两类
1.原子类型:不可以载分解的基本数据类型
2.结构类型:由若干个数据类型构成,可以进行再分解。例如。整型数组是由若干个整型数据组成的
抽象:指抽取出事物具有的普遍性的本质,它要求抽出问题的特征而忽略非本质的细节,
是对具体事物的一个概括。抽象是一种思考问题的方式,它隐含了复杂的细节
抽象数据类型:(Abstract Data Type,ADT)是指一个数学模型及定义在该模型上的一组操作。
抽象数据类型的定义仅取决于它的一组逻辑特性,与其在计算机内部的表现形式无关。
抽象数据类型格式:
ADT 抽象数据类型名
Data
数据元素之间逻辑关系的定义
Operation
操作
endADT
线性表的抽象数据类型:将数据类型与操作组合在一起
常见操作:Operation
1.InitList(*L):初始化操作,建立一个空的线性表L
2.ListEmpty(L):判断线性表是否为空,若线性表为空则返回true,非空则返回false
3.ClearList(*L):清空线性表
4.GetElem(L,i,*e):将线性表L中的第i个元素值返回给e
4.LocateElem(L,e):在线性表中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号,表示成功,如果查找失败,则返回0。
5.ListInsert(*L,i,e):在线性表L中的第i个位置插入元素e
6.ListDelete(*L,i,*e):删除线性表L中的第i个元素,并用e返回其值
7.ListLength(L):返回线性表L的元素个数
问:将线性表A、B进行并集操作
void unionL(List *A,List B){
ElemType e;
len_A=ListLength(*A);
len_B=ListLength(B);
for(i = 0 ; i < len_B ; i++ ) {
GetElem(B,i,&e);
if(!LocateElem(*A,e))
ListInsert(*A,++len_A,e);
}
}
线性表的顺序存储结构
----------------------------------------------
顺序存储结构代码
#define MAXSIZE 20
typedef int ElemType
typedef struct
{
ElemType data[MAXSIZE];
int length; //定义当前线性表的长度
}Sqlist;
//本质上为对数组的封装
顺序存储结构封装需要的三个属性
1.存储空间的起始位置,数组data,它的存储位置就是线性表存储空间的存储位置
2.线性表的最大存储容量,数组的长度MAXSIZE
3.线性表的当前长度,length
注意:数组的长度是存放线性表存储空间的总长度,一般初始化后不发生变化(可动态扩容),而线性表的当前长度是线性表中元素的个数,会变化
地址的计算方法
假设ElemType占用的是C个存储单元(字节大小),线性表中的第i+1个元素与第i个元素的存储位置关系
为(LOC表示获得存储位置的函数):LOC(i+1)=LOC(i)+C LOC(i)=LOC(1)+(i-1)C
由公式可以推导出线性表的存储时间性能为O(1),对于这种存储结构,一般称作随机存储结构。
获取数据元素:时间复杂度O(1)
Status GetElem(SqList L, int i, ElemType *e){
if(L.lenght < 1 || i > L.length || i < 1) //获取元素不存在时
{
return 0;
}
*e = L.data[i-1]; //获取元素
return 1;
}
插入数据元素:时间复杂度O(n)
Status ListInsert(SqList L, int i, ElemType e){
int k;
if(L->length == MAXSIZE) return0; //当线性表满时报错
if( i < 1 || i > L->length) reruen 0; //当i不在范围时报错
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 1;
}
删除数据元素:时间复杂度O(n)
Status ListDelete(SqList L, int i, ElemType *e){
if(L->length == 0) return 0; //如果表长为0,则表示空表,无法删除
if(i < 1 || i > L->length) return 0; //判断删除元素是否存在
*e = L->data[i-1];
if(i < L->length){ //如果删除元素存在
for(k = L->length - 1; k >= i - 1; k--)
L->data[k-1] = L->data[k]; //删除元素位置后的所有元素向前移一位
}
L->length--;
return 1;
}
线性表顺序存储结构的优缺点
优点:1.无需为表示表中元素之间的逻辑关系而增加额外的存储空间
2.可以快速存取表中任意位置的元素
缺点:1.插入和删除操作需要移动大量的元素
2.当线性表长度变化较大时,难以确定存储空间的容量
3.容易造成存储空间的碎片化
线性表的链式存储结构
-----------------------------------------
特点:用一组任意的存储单元存储线性表的数据元素,该存储单元可以在内存中未被占用的任意位置
链式存储结构需要存储数据元素本身和其后继元素,后继元素为下一个数据元素的地址指针
存储数据元素的域叫做数据域;
存储后继元素的域叫做指针域;
指针域中存储的信息叫做指针域链;
指针域与数据域组成存储映像,也称节点(node);
由于链表中的每个节点只包含一个指针域,所以称为单链表;
n个节点链接成链表即为线性表的链式存储结构; 链表中的第一个节点的存储位置叫做头指针,最后一个节点指针为空(null)
头指针与头节点
----------------------------------------------
头结点的数据域不存储任何东西
头指针:1.头指针是链表指向第一个节点的指针,若链表有头节点,则是指向头节点的指针
2.头指针具有标识作用,所以常用头指针作为链表的名字(指针变量名)
3.无论链表是否为空,头指针均不为空
4.头指针是链表的必要元素
头结点:1.为操作方便与统一而设立,放在第一个元素节点之前,其数据域无意义,可用来存放链表的长度
2.有了头结点对第一元素节点插入和删除第一节点的操作与其他节点的操作就统一了
3.头结点不一定是链表的必要元素
单链表存储结构
----------------------------------------------
单链表描述:建议将指针的*号尽量放在右边,避免引起歧义
typedef struct Node
{
ElemType date; //数据域;
struct Node* Next; //指针域;节点的指针名叫next
}Node;
typedef struct Node* LinkList; //Node*取别名为LinkList
假设p是线性表中第i个元素的指针,则该节点使用p->data来表示该节点的数据域,即数据元素;p->next表示该节点的指针域,p->next为指针单链表的读取:时间复杂度O(n)
核心思想:工作指针后移
Status GetElem(LinkList L, int i, ElemType *e){
int j;
LinkList p; //申明指针p,用来迭代指向查询的元素
p = L->next; //p指向当前节点
j = 1;
while(p && j<i){ //p为指向非空节点,且不超出链表范围
p = p->next;
j++;
}
if( !p || j > i) return 0; //非空指针域超出链表范围
*e = p->data;
return 1;
}
单链表的插入:时间复杂度O(1)
Status ListInsert(SqList *L. int i, ElemType e){
int j = 1;
LinkList p,s;
p = *L;
while(j < i && p){ //寻找插入位置
p = p->next;
j++;
}
if( !p || j > i) return 0;
s = (LinkList)malloc(sizeof(Node)); //生成新的节点
s->data = e;
/*
错误写法:p->next = s;
s->next = p->next;
错误原因:最终 == s->next=s
*/
s->next = p->next;
p->next = s;
return 1;
}
单链表的删除:时间复杂度O(1)
Status ListDelete(SqList L, int i, Elemtype e){
int j = 1;
LinkList p,q;
p = *L;
while( p && j < i){ //查找删除位置
p = p->next;
j++;
}
if(!(p->next) || j > i) return 0; //超出范围,异常处理
q = p->next;
p-next = q->next; //删除节点,拿下一个节点信息进行替换
*e = q->data; //接收删除节点中存放的数据元素
free(q); //释放删除节点所占用的空间
}
对于链式存储结构和顺序存储结构,当对数据进行读修改写操作越频繁,单链表的效率优势越明显单链表的整表创建
----------------------------------------------
头插法
头插法从一个空表开始,生成新节点,读取数据放置到新节点的数据域中,将新节点插入到当前链表的表头上,知道结束为止
void CreateListHead(LinkList *L, int n)
{
LinkList p;
int i;
srand(time(0)); //模拟产生随机数
*L = (LinkList)maslloc(sizeof(Node));
(*L)->next = null;
for(i = 0; i < n; i++){
p = (LinkList)malloc(sizeof(Node)); //生成新节点
p->data = rand() % 100 + 1; //真正产生随机数1-100
p->next = (*L)->next; //(*L)->next为表头
(*L)->next = p; //将P作为头
}
}
尾插法
新生成的节点插入到链表的末尾
void CreateLinkListTail(SqList *L, int n)
{
LinkList p;
int i;
srand(time(0)); //产生随机数种子
(*L)->next = null; //将头结点赋值为null
for(i = 0; i < n; i++){ //n代表的是节点数
p = (LinkList)malloc(sizeof(Node)); //产生新的节点,赋值给用于迭代插入的中间节点p
p->data = rand() % 100 + 1; //节点p插入数据
(*L)->next = p; //链表的尾部(迭代节点后)插入新节点
(*L) = p; //将已有链表的的最后节点进行标记,方便下次插入
}
}
单链表的整表删除
----------------------------------------------
本质:释放内存
void ClearLinkList(SqList *L)
{
LinkList p,q;
p = (*L)->next; //p为第一个元素节点
while(p){ //当p为非空时,进行删除p节点的操作
q = p->next; //将p的下一个节点进行删除
free(p); //释放空间
p = q; //迭代删除,p作为迭代节点
}
(*L)->next = null; //删除完成的条件
return 1;
}
注意:如果不使用中间迭代节点q时,删除p后,p的下一个节点信息将无法记录,无法进行后续的操作
单链表存储结构和顺序存储结构的比较
----------------------------------------------
存储分配方式 | 时间性能 | 空间性能 | |
顺序结构 | 连续存储单元存储线性表 | 查找:O(1) 插入和删除:O(n) | 预先分配存储空间,容易导致资源分配溢出或匮乏 |
单链表结构 | 任意位置存储,每个节点包含下一个节点的地址信息 | 查找:O(n) 插入和删除:O(1)(查找到插入或删除的位置的时间复杂度为O(n)) | 不需要预分配存储空间,可以利用任意的存储单元进行存储 |
使用:若线性表需要频繁查找,很少使用插入或删除操作时,宜采用顺序存储结构,反之采用单链表结构
静态链表
----------------------------------------------
用数组描述的链表叫做静态链表,该描述方法叫做游标实现法
存储结构:
#define MAXSIZE 100
typedef struct
{
ElemType data; //数据
int cur; //游标(cursor)
}Component,StaticLinkList[MAXSIZE];
特点: 1.每个节点包含数组的下标、数据、游标
2.游标为链表中下一个节点的数组下标,相当于地址
3.第一个节点的[0]数据为空,游标指向第一个备用节点的数组下标
4.最后一个节点[n]数据为空,游标指向第一个有数据元素的节点的数组的下标
5.有数据的最后一个节点的游标为0,指向第一个空节点
对静态链表进行初始化==数组的初始化
Struct InitList(StaticLinkList space){
int i;
for( i = 0; i < MAXSIZE - 1; i++)
space[i].cur = i + 1; //游标为链表中下一个节点的数组下标,相当于地址
space[MAXSIZE - 1].cur = 0; //初始化数组数据均为空,所以最后一个的游标指向第一个节点下标为0
return 1;
}
静态链表的插入
1.获取空闲分量的下表
int Malloc_SLL(StaticLinkList sapce)
{
int i = space[0].cur;
if([sapce[0].cur])
space[0].cur = sapce[i].cur; //使用备用元素进行插入操作,插入完成后将它的下一个分量作为备用
return i;
}
2.进行插入操作
Status ListInsert(StaticLinkList L, int i, Elemtype e){ //i表示插入位置,第i个元素之前
int j,k,l;
k = MAXSIZE - 1; //数组的最后一个元素
if(i < 1 || i > ListLength(L) + 1) return 0;
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 1;
}
return 0;
}
静态链表的删除
Status ListDelete(StaticLinkList L, int i){
int j, k;
k = MAXSIZE - 1;
if(i < 1 || i > ListLength(L)) return 0;
for( j = 1; j <= i - 1; j++)
k = L[k].cur; //取出要删除元素的下标
j = L[k].cur; //将要删除节点的游标赋值给j
l[k].cur = L[j].cur; //将要删除节点的后一个节点的下标赋值给前一个节点的游标
Free_SLL(L,j); //清空节点
return 1;
}
void Free_SLL(StaticLinkList sapce, int k){
sapce[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){ //判断游标是否为0,为0则为第一个元素,一次遍历成功,退出
i = L[i].cur;
j++;
}
return j;
}
静态链表的优缺点
优点:在插入和删除操作时,只需要修改游标,不需要移动元素
缺点:没有解决连续分配带来的表长难以确定的问题,失去顺序结构随机存取的特性
腾讯面试
----------------------------------------------
题:快速找到位置长度单链表的中间节点
答:(1)普通方法
遍历一遍单链表,得出链表长度L,再次遍历到1/2处,得出中间节点
算法复杂度:O(L+L/2) = O(3L/2)
(2)快慢指针
设置两个指针L1、L2,开始都指向单链表的表头,L1的移动速度是L2的两倍,当L1移动到末尾节点使,L2在中间节点处
Status GetMidNode(LinkList L, Elemtype *e){
LinkList L1,L2;
L1 = L2 = L;
while(L1->next == !null){
if(L1->next->next != null){
L1 = L1->next->next; //L1的速度是L2的两倍
L2 = L2->next;
}else{
L1 = L1->next; //L1指向最后一个节点时
}
}
*e = L2->data;
return 1;
}
循环链表
----------------------------------------------
定义:将单链表中终端节点的指针端由空指针改为指向头节点,是整个单链表形成一个换,这种头尾相接的单链表称为单循环链表,简称循环链表
注意:并不是循环链表一定要有头节点
本质:循环链表与单链表的主要差异在于循环判断空链表的条件上,原来判断head->next是否为null,现在则是head->next是否等于head。
终端节点用尾指针rear指示,查找终端节点的时间复杂度为O(1)(在定义循环链表时就标记出尾指针),开始节点则是rear->next->next,时间复杂度为O(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);
//将从头开始,将头结点的头指针赋值给target,查找尾节点,target->next不是头节点的指针,则继续查找,直到找出尾节点
temp = (node*)malloc(sizeof(struct CLinkList)); //生成新节点
if(!temp) exit(0);
temp->data = item;
temp->next = *pNode; //循环链表指向头节点
target->next = temp;
}
}
}
循环链表的插入
/*链表存储结构的定义*/
typedef struct CLinkList{
int data;
struct CLinkList *next;
}node;
/*插入节点*/
void ds_insert(node **pNode, int i){ //**pNode为第一个节点,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);
///将从头开始,将头结点的头指针赋值给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;
}
}
循环链表删除节点
void ds_delete(node **pNode, int i){//*pNode为第一节点,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;
free(temp;)
}else{ //删除其余节点
target = *pNode;
for(; j < i - 1; ++j) target = target->next;
temp = target->next;
target->next = temp->next;
free(temp);
}
}
循环链表查找节点
int da_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) return0; //表示不存在该元素
else return i;
}
约瑟夫问题
----------------------------------------------
#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
s->data = i++; //将i的值赋值给对应的s
p->next = s; //将节点s插入链表中
p = s; //p作为迭代节点,不断向其后插入新的节点
}
s->next = head->next; //将链表的尾部节点指向头节点,形成循环链表
}
free(head); //释放头节点
return s->next;
}
int main(){
int n = 41; //链表节点个数
int m = 3;
int i;
node *p = create(n); //创建指针p
node *temp; //创建临时指针temp
m = n % m; // m = 2;
while(p != p->next){ //链表非空时
for(i = 1; i < m-1; i++) p = p->next; //迭代节点
printf("%d->",p->next->data); //kill,删除第m个节点
temp = p->next; //删除p的下一个节点
p->next = temp->next; //p指向删除节点的下一个节点
free(temp); //释放删除节点的内存
}
}
作业:
编号为1-N的N个人按顺时针方向围坐成一圈,每人手中持有一个密码,开始人选一个正整数作为报数上限M,
从第一个人按顺时针方向自1开始顺序报数,报道M时停止报数,报M的人出列,将他的密码作为新的M值,
从他顺时针方向上的下一个人开始从1报数,如此下去,纸质所有人全部列出为止
code:
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;
int j;
if(0 != n){
while(i < n){
s = (node*)malloc(sizeof(node));
s->data = scanf("%d",j);
p->next = s;
p = s;
}
s->next = head->next;
}
free(head);
return s->next;
}
int main(){
int n = 16;
int m;
node *p = create(n);
node *temp;
while(p != p->next){
for(int i = 1; i < m - 1; i++){
p = p->next;
}
m = p->data;
temp = p->next;
p->next = temp->next;
free(temp);
}
}
循环列表的特点
判断循环链表是否为空:rear->next = rear
无需增加存储量,灵活改变链表,便可以对表进行处理
题:实现将两个线性表连接成一个线性表
code:
LinkList a_head = a->next;//保存a的头
a->next = b->next->next; //a的尾指向b的头
free(b->next); //释放b的头
b->next = a_head; //b的尾指向a的头
环的定义:链表中的尾节点指向链表中的某个节点
魔术师发牌问题
----------------------------------------------
魔术师将最上面的那张牌数为1,把他翻过来正好是黑桃A,将黑桃A放在桌子上,
第二次数1,2将第一张牌放在这些牌的下面,第二张牌翻过来正好是黑桃2,也
将它放在桌子上依次将13张牌拿出,准确无误。(翻出的牌仍在13张中)
#include <stdio.h>
#include <stdlib.h>
#define Cardnumber 13
typedef struct node
{
int data;
struct node *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; //计数工作,实现在数1……13之间间隔翻牌
p = head;
p->data = 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)
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;
}
拉丁方阵问题
题:拉丁方阵是一种nxn的方阵,方阵中恰有n种不同的元素,每种元素恰有n个,并且每种元素在一行和一列中恰好出现一次。
code:
#include <stdio.h>
#include <stdlib.h>
#define Number 3;
typedef struct node
{
int data;
struct node *next;
}sqlist, *linklist;
linklist CreateLinkList(){
linklist head = NULL;
linklist s,r;
int i;
r = head;
for(i = 1; i <= Number; i++){
s = (linklist*)malloc(sizeof(node));
s->data = i;
if(head == NULL) head = s;
else r->next = s;
r = s;
}
r->next = head;
return head;
}
int main(){
linklist p,q;
q = head;
for(int j = 0; j < number; j++){
for(int i = 0; i < Number; i++){
printf("%d",p->data);
if(i == nub=mber - 1) break;
else p = p->next;
}
p = q->next;
q = q->next;
}
}
双向链表
----------------------------------------------
结构代码:
typedef struct DualNode{
ElemType data;
struct DualNode *prior; //前驱节点
struct DualNOde *next; //后继节点
}DualNode, *DuLinkList;
双向链表的插入
s->next = p;
s->prior = p->prior;
p->prior->next = s;
p->prior = s;
双向链表的删除
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);
优点:提高时间性能,增加空间使用题:实现用户输入一个数使得26个字母的排列发生变化,例如用户输入3,输出结果DEFGHIJKLMNOPQRSTUVWXYZABC
同时支持负数,例如用户输入-3,输出结果XYZABCDEFGHIJKLMNOPQRSTUVW
code:
#include <stdio.h>
#include <stdlib.h>
#define num 26
#define char ElemType; //定义Elemtype为char类型
#define int Status; //定义返回值int类型的别名为status
/*定义双向链表结构*/
typedef struct DualNode
{
Elemtype data;
struct DualNode *prior;
struct DualNode *next;
}DualNode, *DuLinkList; //定义结构体别名
Status InitList(DuLinkList *L) //定义初始化函数InitList,局部变量为结构体类型DualLinkList名为*L
{
DualNode *p,*q; //定义指针*p,*q,其中p为迭代指针,q为每次新创建的节点
int i;
(*L) = (DuLinkList *)malloc(sizeof(DualNode)); //创建链表L的头节点
if(!(*L)) return 0; //如果发生异常创建失败,返回0
(*L)->next = (*L)->prior = NULL; //将头节点的前驱指针和后继指针赋值为空,用来表征头节点
p = (*L);
for(i = 0; i < num; i++){
q = (DuLinkList)malloc(sizeof(DualNode)); //创建新节点p
if( !q ) return 0; //如果发生异常创建失败,返回0
q->data = 'A' + i; //将数据元素放入节点中
q->prior = p->next; //形成节点之间的链接
p->next = q->next; //保护新生成节点的next指针,为其赋值为NULL
p = q; //进行迭代,p指向现有链表的最后一个节点,q为新生成的节点
}
p->next = (*L)->next;
(*L)->next->prior = p;
return 1;
}
/*设计实现功能的算法*/
void Caesar(DuLinkList *L,int i){
if(i > 0){
do{
(*L) = (*L)->next;
}while(--i);
}
if(i < 0){
do{
(*L) = (*L)-next;
}while(++i);
}
}
int main(){
DuLinkList L;
int i,n;
InitList(&L);
printf("请输入一个数:");
scanf("%d", &n);
Caesar(%L,n);
for(i = 0; i < num; i++){
L = L->next;
printf("%c",L->data);
}
return 1;
}
Vigenere(维吉尼亚加密算法)