线性表
线性表(顺序存储)
一.线性表的抽象数据类型定义
- ADT 线性表 (list)
- Data 线性表的数据对象集合为 {a1,a2,a3,a4…,an},每个元素的类型均为DataType。其中,除了第一各元素a1以为每个元素有且只有一个直接前驱元素,除了最后一个元素an以外,每个元素有且只有一个直接后继元素。数据元素之间的关系是一对的关系。
二.operation
InitList(*L):初始化操作,生成一个空的线性表L。
ListEmpty(L):判断是否为空表,如果是就返回ture,如果不是就返回lfalse。
ClearList(*L):将线性表清空。
GetElem(L,i,*e):将线性表L中的第i个节未知元素值返回给e。
LocateElem(L,e):在线性表中查找与给定值e相等的元素,如果查找成功,返回元素在表中的序号表示成功;否则返回0表示失败。
ListInsert(*L,i,e):在线性表L中第i个位置插入新的元素e。
ListDelete(*L,i,*e):删除线性表L中第i个位置元素,并返回其值给e。
Listlength(L):返回线性表L的元素个数。
三.常用组合
- ListLength(L);
- GetElem(L,i,*e);
- LocateElem(L,e);
- ListInsert(*L,i,e);
线性表的顺序存储结构
- 指的是用一段连续的存储单元一次存储线性表的数据元素。
- 两种存储结构:顺序存储和链式存储。
- 顺序存储
#define MAXSIZE 20
typedef int ElemType;
typedef struct
{
E;lemType data[MAXSIZE];
int length; //线性表当前长度
} SqList;
- 顺序存储结构需要三个属性:
- 存储空间的起始位设置,数组data,它的存储位置就是线性表存储空间的存储位置
- 线性表的醉的存储容量:数组的长度MaxSize。
- 项行标的当前长度:length。
- 强调一下数组的长度和线性表的长度需要区分:数组长度是存放线性表的存储空间的总长度,一般初始化后是不变的,而线性表的当前长度是线性表的元素的个数,是会变化的。
- 举例得到一个元素值
- 又例如插入操作
- 删除操作
- 又例如插入操作
线性表(链式存储)
一.头指针和头结点
- 头指针
- 头指针是指链表指向的第一个节点的指针,若链表有头节点,则是指向头结点的指针。
- 头指针具有标识作用,所以常用头指针冠以链表烦人名字(指针变量的名字)。
- 无论链表是否为空,头指针均不为空。
- 头指针是链表的必要元素。
- 头节点
- 头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(但也可以用来存放链表长度)。
- 有了头结点,对在第一元素节点之前插入节点和删除结点的操作就统一利了。
- 头结点不一定是头节点不一定是链表的必要元素。
- 举例:结构指针描述单链表
typedef struct Node
{
ElemnType data; //数据域
struct Node* next; //指针域
}Node
typedef stuct Node *LinkList;
二.运用
基础调用
void CreateLinkListHead(LinkList *L,int n)
{
LinkList p;
int i;
srand(time(0));//初始化随机数种子
*L = (LinkList)malloc(sizeof(Node));
(*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;
}
}
尾插法建立单链表(CreateLinkListTail)
void CreateLinkListTail(LinkList *L,int n)
{
LinkList p,r;
int i;
srand(time(0));//初始化随机数种子
*L = (LinkList)malloc(sizeof(Node));
r = *L;
for(i=0;i<n;i++)
{
p=(Node *)malloc(sizeof(Node));
p->data = rand() % 100+1;
r->next = p;
r=p;
}
r->next = NULL;
}
整表删除(ClearList)
线性表(静态链表)
静态链表的基础
- 对数组的第一个和最后一个元素做特殊处理,他们的data不存放数据。
- 我们通常未使用的数组元素成为备用链表。
- 数组的第一个元素,及下标为0的那个元素的cur(游标)就存放备用链表的从第一个结点的下标
- 数组的最后一元素,及下标为MAXSIZE-1的cur则存放第一个有数值的元素的下标,相当于单链表的头结点的作用。
静态链表的插入操作
- 获取空闲分量的下标:
int Malloc_SLL(StaticLinkList space)
{
int i = space[0].cur;
if(space[0].cur)
space[0].cur = space[i].cur;//下一个分量用来作为备用。
return i;
}
静态链表的删除操作
- 静态链表优缺点的总结
- 优点
- 在插入和删除操作时,只需要改变游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量的元素的缺点。
- 缺点
- 没有解决连续存储分配(数组)带来的表厂难以确定的问题
- 失去了顺序存储结构随机存取的特性
- 优点
单链表腾讯面试题
题目:快熟找到未知长度单链表的中间结点。
(运用快慢指针:设置两个指针,*search、mid 都执向单链表的头节点。其中 search的移动速度是mid的两倍,当search指向末尾结点的时候,mid正好就在中间,标尺思想)
Status GetMidNode(LinkList L,ElemType *e)
{
LinkList search,mid;
mid = search = L;
while(search->next != NULL)
{
if(search->nemxt->next != NULL)
{
search = search->next->next;
mid=mid->next;
}
else
{
search = search ->next;
}
}
*e = mid->data;
}
循环链表
单循环链表
简而言之 单链表循环就是把单链表原本的指向NULL的尾结点指向了头节点,让它从链变成了环。
在判断的时候 对于ptail->next != NULL或者 ptail->nex->next != NULL 改换成 ptail->next != phead或者 ptail->nex->next != phead即可
- 如下示例
/*初始化循环链表*/
void ds_init(node **Node)
{
int item;
node *temp;
node *target;
printf("输入节点的值 输入0完成初始化\n");
while(1)
{
scanf("%d",&item);
fflush(stdin);
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->date = item;
temp ->next = *pNode;
target->next = temp;
}
}
}
- 插入新结点
/*链表存储结构的定义*/
typedef struct CLinkList
{
int data;
struct CLinkList *next;
}node;
/*插入结点*/
/*参数:链表的第一个节点,插入的位置*/
void ds_insert(node **pNode,int i)
{
int temp;
node *temp;
node *target;
int item;
int j = i;
printf("输入节点的值 输入0完成初始化\n");
scanf("%d",&item);
if(i == 1)
{//插入新的结点作为第一个节点
temp= (node *)malloc(siziof(truct ClinkList))
if(!temp)
exit(0);
temp->date = item ;
/*寻找最后一个结点*/
for(target = (*pNode);target->next != (*pNode);target = target -> next)
;
temp->next = (*pNode);
traget->next = temp;
*pNode = temp;
}
else
{
traget = *pNode;
for(;j<(i-1);target = ++j)
{
traget = target->next;
}
temp = (node *)malloc(sizeof(struct CLinkList));
if(!temp)
exit(0);
temp->date = item;
p=target->next;
target ->next = temp;
target->next = p;
}
}
}
/*删除结点*/
void ds_delete(node **Node,int i)
{
node *target;
node *temp;
int j = i;
if(i == 1)
{
//删除的是第一个结点
/*找到最后一个节点*/
for(target=*pNode;target->next !=*pNode;target=target->next)
;
temp = *pNode;
*pNode = (*pNode)->next;
target->next = *pNode;
free(temp);
}
}
/*查找*/
int ds_search(node *pNode ,int elem)
{
node *target;
int i = 1;
for(target=pNode;target->date != elem&&target->next != pNod;++i )
{
target = target->next;
}
if(targe->next == NULL)
return 0;
else
{
return i;
}
}
约瑟夫问题
约瑟夫问题
一个旅行社要从n个旅客中选出一名旅客,为他提供免费的环球旅行服务。旅行社安排这些旅客围成一个圆圈,
从帽子中取出一张纸条,用上面写的正整数m(<n)作为报数值。游戏进行时,从第s个人开始按顺时针方向自1开始顺序报数,
报到m时停止报数,报m的人被淘汰出列,然后从他顺时针方向上的下一个人开始重新报数,如此下去,直到圆圈中只剩下一个人,
这个最后的幸存者就是游戏的胜利者,将得到免费旅行的奖励。
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
int date;
struct student *next;
} node;
node *createLinkList(int n)
{
node *p = NULL,*head,*s;
int i =1;
head = (node*)malloc(sizeof(struct node));
p=head;
if(0!=n)
{
while(i<=n)
{
s=(node*)malloc(sizeof());
s->date=i++;
p->next=s;
p=s;
}
s->next = head->next;
}
free(head);
return s->head;
}
int main()
{
int n=41;
int m=3;
int i;
node *p = creat(n);
node *temp;
n %= m;
while(p!=p->next)
{
for(i=1;i<n;i++)
{
p=p->next;
}
printf("%d->",p->next->data);
temp=p->next;
p->next=temp->next;
free(temp);
p=p->next;
}
printf("%d",p->date);
return 0;
}
循环链表
循环链表的特点
- 表中最后一个节点的指针域指向头结点,整个单链形成一个环。
- 循环链表操作和线性链表基本一致,差别仅在于算法中的循环条件不同,由p或p->next是否为空,而是他们是否等于头指针。但有的时候,若在循环链表中设立尾指针而不设立头指针,可以使某些操作简化,例如两个线性表合成一个表的时候,仅需要将一个表的尾指和另一个表的头指针相接。
- 例如
实现两个线性表(a1,a2,a3…aN)和(b1,b2,b3…bN)的运算。
分析:
若在单链表或头指针表示的单循环表上做这种链接操作,都需要遍历第一个链表,找到结点an,然后将节点b1链接an的后面,其执行时间O(n)。
若在尾指针表示的单循环链表上实现,则只需要修改指针,无需遍历,其执行时间是0(1)。
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从6走到3时,因用了6步,此时若q从head出发,则只需要走两步就到达3,因而步数不等,出现矛盾,存在环。
- 方法二:使用p,q两个指针,p每次向前走一步,q每次向前走两步,若某个时候p == q,则存在环。
魔术师发牌问题
#include<stdio.h>
#include<stdlib.h>
#define CareNumber 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 ==NILL)
{
head = s;
}
else
{
r->next = s;
}
r = s;
}
r->next = head;
}
//发排顺序计算
void Magician(linklist head)
{
linklist p;
int j;
int CountNumber = 2;
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)
{
linklist ptr = *list;
linklist buff[Cardnumber];
int i =0;
while(i < CardNumber)
{
buff[i++] = ptr;
}
for(i=0;i<CardNumber;i++)
free(buff[i]);
*list = 0;
}
int mian()
{
linklist p;
int i;
p = CreatLinkList();
Magcian(p);
printf("顺序如下\n");
for(i = 0;i<Cardnumber;i++)
{
printf("黑桃%d",p->data);
p = p->next;
}
DestoryList(&p);
return 0;
}
拉丁方阵问题
双向链表
结构特点
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);
双向链表实践
#include<stdio.h>
#include<stdlib.h>
#define OK 1
#define ERROR 0
typedef char ElemType
typedef struct DualNode
{
ElenType data;
struct DualNode *prior;
struct DualNode *next;
}DualNode,*Dulinklist;
Struct InitList(DuLinkList *L)
{
DualNode *p,*q;
int i;
*L = (DuLinkList)malloc(sizeof(DualNode));
if(!(*L))
{
return ERROR;
}
(*L)->next = (*L)->prior = NULL;
for(i = 0;i < 26; i++)
{
q = (dulNode *)malloc(sizeof(DualNode));
if(!(*q))
{
return ERROR;
}
q -> data = 'A'+i;
q -> prior = p;
q -> next = p->next;
p -> next = q;
p = q;
}
p->next = (*L)->next;
(*L) -> next -> prior = p;
return OK;
}
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 mian()
{
DuLinkList p;
int i,n;
initList(&L);
printf("请输入一个整数:");
scanf("%d",&n);
printf("\n");
Caeser(&L,n);
for( i = 0;i < 26;i++)
{
L = L->next;
printf("%d",L->data);
}
printf("\n");
return 0;
}