1 线性表的概念
线性表(List)概念:由n个类型相同的数据元素组成的有限序列。(类似排队,具有线一样性质的结构。)
线性表性质:
- 同一性:元素类型相同
- 有穷性:有限元素
- 有序性:存在序偶关系
2 线性表的顺序存储
2.1 顺序表存储结构
用一组地址连续的存储单元依次存储线性表中的各个元素,其物理结构和逻辑结构一致。可以将顺序表归纳为:“关系线性化,结点顺序存”。
顺序表第i个元素所在地址的计算:Loc(ai) = Loc(a1) + (n-1) * k。其中Loc(a1) 称为基地址,k是每个元素所占单元数。
2.2 顺序表抽象数据类型定义
C语言代码如下:
#define MAXSIZE 255
typedef struct {
ElemType elem[MAXSIZE];
int last; //记录线性表最后一个元素在elem[]中的位置(下标),空表为-1
}SeqList;
说明:
- Elem 应该根据实际情况而定,可以是int、double、char等
- 变量L的定义和使用的两种方式:
- 通过变量定义语句:
- SeqList L;
- 利用L.elem[i-1]访问第i个元素,利用L.last得到顺序表最后一个元素的下标,L.last+1是顺序表长度。
- 通过指针变量定义语句:
- SeqList L1, *L; L = &L1;
- 利用L -> elem[i-1]访问第i个元素,利用L -> last +1得到顺序表长度
- 通过变量定义语句:
2.3 顺序表基本运算
2.3.1 查找
int SeqSearch(SeqList L, ElemType e)
{
int i = 0;
while((i<=L.last)&&(L.elem[i]!=e))
{
i++;
}
if(i<=L.last)
return (i+1); //i是e元素在elem[]中的下标,i+1是e元素在线性表中的位置
else
return(-1); //e不在elem[]中,返回-1
}
2.3.2 插入
#define OK 1
#define ERROR 0
int SeqInsert(SeqList *L, int i, ElemType e)
/*
在顺序表第i个位置之前插入一个元素e
*/
{
int k;
if((i<1)||(i>L->last+2))
{
printf("插入位置i值不合法");
return ERROR;
}
if(L->last >= MAXSIZE -1)
{
printf("表已满,无法插入");
return ERROR;
}
for(k = L->last; k>= i-1; k--)
{
L->elem[k+1] = L->elem[k];
}
L->elem[i-1] = e;
L->last++;
return OK;
}
2.3.3 删除
#define OK 1
#define ERROR 0
int SeqDelete(SeqList *L,int i, ElemType *e)
/*
删除在顺序表第i个位置的元素,用e返回其值
*/
{
int k;
if((i<1)||(i>L->last+1))
{
printf("删除位置不合法");
return ERROR;
}
e = L->elem[i-1];
for(k = i; k<= L->last; k++)
{
L->elem[k-1] = L->elem[k];
}
L->last--;
return OK;
}
2.3.4 合并
前提:
有两个顺序表LA和LB,其元素均为非递减有序排列,编写一个算法,将它们合并成一个顺序表LC,要求LC也是非递减有序排列。
算法思想:
设表LC是一个空表。设置两个指针i、j分别指向表LA和LB中的元素。
- 若LA.elem[i] ≤ LB.elem[j],则先将LA.elem[j]插入到表LC中
- 若LB.elem[i] ≤ LA.elem[j],则先将LB.elem[i]插入到表LC中
- 最后再将未扫描完的表中剩余的所有元素放到表LC中
void MergeSeqList(SeqList *LA, SeqList *LB, SeqList *LC)
{
int i=0,j=0,k=0,l;
while(i<=LA->last && j<=LB->last)
{
if (LA->elem[i] <= LB->elem[j])
{
LC->elem[k] = LA->elem[i];
i++;
k++;
}
else
{
LC->elem[k] = LB->elem[j];
j++;
k++;
}
}
while(i<=LA->last)
{
LC->elem[k] = LA->elem[i];
i++;
k++;
}
while(j<=LB->last)
{
LC->elem[k] = LA->elem[j];
j++;
k++;
}
LC->last = LA->last+LB->last+1;
}
2.4 顺序存储的优点和缺点
2.4.1 优点
- 无需为表示结点间的逻辑关系而增加额外的存储空间。
- 可方便地随机存取表中的任一元素。
2.4.2 缺点
- 插入或删除运算不方便,除表尾的位置外,在表的其它位置上进行插入或删除操作都必须移动大量的结点,效率较低。
- 由于顺序表要求占用连续的存储空间,存储分配只能预先进行静态分配。因此当表长变化较大时,难以确定合适的存储规模。
3 线性表的链式存储
3.1 链表的概念
概念:用一组任意的存储单元存储线性表的数据元素。每一个数据元素就是一个结点,包括数据域和指针域。
结点:数据元素信息(数据域:data)+ 后继结点的地址(指针域:next)
3.2 单链表
概念:只有一个next指针域的线性链表。链表里每个结点的存储地址存放在前驱结点的指针域中。由于线性表第一个结点无前驱,所以应该设一个头指针H指向第一个结点。由于线性表最后一个结点没有直接后继,指定单链表最后一个结点的指针域为空。
3.2.1 单链表抽象数据类型定义
C语言代码如下:
typedef struct Node{
ElemType data;
struct Node *next; //记录线性表最后一个元素在elem[]中的位置(下标),空表为-1
}Node, *LinkList;
说明:LinkList与Node * 同为结构指针类型,两种类型等价。通常习惯用LinkList说明指针变量,强调它是某个单链表的头指针变量。用Node*来定义单链表中结点的指针,表明指向单链表中结点的指针变量。
3.2.2 初始化单链表
InitList(LinkList *L)
{
*L = (LinkList)malloc(sizeof(Node)); //建立头节点
(*L)->next = NULL; //建立空的单链表L*
}
3.2.3 单链表的建立
头插法建表:
void GreatFromHead(LinkList L)
{
Node *s;
char c;
int flag = 1;
while(flag)
{
c = getchar();
if(c!='$')
{
s = (Node * )malloc(sizeof(Node));//建立新结点s
s->data = c;
s->next = L->next;
L->next = s;
}
else
flag = 0;
}
}
尾插法建表(推荐):
void GreatFromTail(LinkList L)
{
Node *s, *r;
char c;
int flag = 1;
r = L;
while(flag)
{
c = getchar();
if(c!='$')
{
s = (Node * )malloc(sizeof(Node));//建立新结点s
s->data = c;
r->next = s;
r = s;
}
else
{
flag = 0;
r->next = NULL;
}
}
}
3.2.4 单链表的查找
按序号查找
Node * Get(LinkList L, int i)
{
if(i<=0)
return null;
int j = 0;
Node *p
p = L;
while((p->next!=NULL)&&(j<i))
{
p = p->next;
j++;
}
if(j==i)
return p;
else
return NULL;
}
按值查找
Node * Locate(LinkList L, ElemType key)
{
Node * p;
p = L->next;
while(p!=NULL)
{
if(p->data!=key)
p = p->next;
else
break;
}
return p;
}
3.2.5 单链表的插入
void InsList(LinkList L, int i, ElemType e)
{
//第i个数据元素之前插入一个数据元素e
if(i<=0)
return ERROR;
Node *pre, *s;
int k = 0;
pre = L;
while(pre!=NULL && k<i-1)
{
pre = pre->next;
k++;
}
if(pre==NULL)
{
printf("插入位置不合理!");
return ERROR;
}
s = (Node*)mallcon(sizeof(Node))
s->data = e;
s->next = pre->next;
pre->next = s;
return OK;
}
3.2.6 单链表的删除
int DelList(LinkList L, int i,ElemType e)
{
Node *pre,*r;
int k=0;
pre = L;
while(pre->next!=NULL&&k<i-1)
{
pre=pre->next;
k++;
}
if(pre->next==NULL)
{
printf("删除结点的位置不合理!");
return ERROR;
}
r = pre->next;
pre->next = r->next;
*e = r->data;
free(r);
return OK;
}
3.2.7 两个单链表的差
前提:假设集合A用单链表LA表示,集合B用单链表LB表示,设计算法求两个集合的差,即A-B。
void Difference (LinkList LA,LinkList LB)
/*此算法求两个集合的差集*/
{
Node *pre, *p, *r;
pre=LA;
p=LA->next; /*p是表中的某一结点,pre始终指向p的前驱*/
while(p!=NULL){
q=LB->next; /*扫描LB中的结点,寻找与LA中*P结点相同的结点*/
while (q!=NULL&&q->data!=p->data)
q = q->next;
if (q!=NULL){
r = p;
pre->next = p->next;
p = p->next;
free(r);
}
else{
pre = p;
p = p->next;
}
}
}
3.3 循环链表
概念:循环链表是一个首尾相接的链表。
特点:
- 表中最后一个结点的指针指向第一个结点或表头结点(如有表头结点的话)。
- 循环链表的运算与线性链表基本一致。但两者判断是否到表尾的条件不同。
3.3.1 循环链表抽象数据类型定义
typedef struct Node
{
ElemType data;
struct Node * next;
}Node,*LinkList;
3.3.2 初始化循环链表
InitCLinkList(LinkList *CL)
{
*CL = (LinkList)malloc(sizeof(Node));
(*CL)->next = *CL;
}
3.3.3 循环链表的建立
void GreateCLinkList(LinkList *CL)
{
Node *rear, *s;
char c;
rear = CL;
c = getchar();
while(c!='$')
{
s = (Node * )malloc(sizeof(Node));
s->data = c;
rear->next = s;
rear = s;
c = getchar();
}
rear->next = CL;
}
3.3.4 循环链表的合并
//循环单链表合并算法1
LinkList merge_1(LinkList LA, LinkList LB)
{
Node *p,*q;
p = LA;
q = LB;
while(p->next!=LA) p = p->next;
while(q->next!=LB) q = q->next;
q->next = LA;
p->next = LB->next;
free(LB);
return(LA);
}
//循环单链表合并算法2
//RA、RB指向表尾
LinkList merge_2(LinkList RA, LinkList RB)
{
Node *p;
p = RA->next;
RA->next = RB->next->next;
free(RB->next);
RB->next = p;
return RB;
}
3.4 双向链表
结点:prior(前驱指针域)、data(数据域)、next(后继指针域)
3.4.1 双向链表抽象数据类型定义
#define ERROR 0
#define OK 1
typedef struct DNode{
ElemType data;
struct DNode *prior,*next;
}DNode,*DoubleList;
3.4.2 双向链表的插入
int DlinkIns(DoubleList L,int i,ElemType e)
{
if(i<0)
return ERROR;
DNode *s, *p;
int k = 0;
p = L;
while(p!=NULL && k<i)
{
p = p->next;
k++;
}
if(p==NULL)
{
printf("插入位置不合理!");
return ERROR;
}
s = (DNode * )malloc(sizeof(DNode));
if(s)
{
s->data = e;
s->prior = p->prior;
p->prior->next = s;
s->next = p;
p->prior = s;
return OK;
}
else
return ERROR;
}
3.4.3 双向链表的删除
int DlinkDel(DoubleList L,int i,ElemType *e)
{
if(i<0)
return ERROR;
DNode *p;
int k = 0;
p = L;
while(p!=NULL && k<i)
{
p = p->next;
k++;
}
if(p==NULL)
{
printf("删除位置不合理!");
return ERROR;
}
*e = p->data;
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);
return OK;
}
4 线性表应用
4.1 一元多项式
一元多项式的表示:Pn(x) = P0 + P1x +P2x^2 +……+Pnx^n
一元多项式存储:
- 顺序存储表示:只存储一元多项式各项的系数,每个系数所对应的指数则隐含在存储系数的顺序表下标中。
- 链式存储表示:存储非零项系数和非零项指数这两项
//链式存储
//一元多项式结点结构定义如下:
typedef struct Polynode {
int coef; //系数
int exp; //指数
struct Polynode * next;
}Polynode,*Polylist;
//用尾插法建立一元多项式的链表
Polylist PolyCreate {
Polynode * head, * rear, * s;
int c, e;
head = (Polynode *)malloc(sizeof(Polynode));
rear = head;
scanf("%d,%d",&c,&e);//输入系数和指数项
while(c!=0)
{
s = (Polynode *)malloc(sizeof(Polynode));
s->coef = c;
s->exp = e;
rear->next = s;
rear = s;
scanf("%d,%d",&c,&e);
}
rear->next = NULL;
return(head);
}
//一元多项式的相加运算
void PolyAdd(Polylist polya,Polylist polyb)
{
Polynode * p, * q, * tail, *temp;
int sum;
p = polya->next;
q = polyb->next;
tail = polya;
while(p!=NULL && q!=NULL)
{
if(p->exp < q->exp)
{
tail->next = p;
tail = p;
p=p->next;
}
else if(p->exp == q->exp)
{
sum = q->coef + q->coef;
if(sum!=0)
{
p->coef=sum;
tail->next = p;
tail = p;
p = p->next;
temp = q;
q = q->next;
free(temp);
}
else
{
temp = p;
p = p->next;
free(temp);
temp = q;
q = q->next;
free(temp);
}
}
else
{
tail->next = q;
tail = q;
q = q->next;
}
}
if(p!=NULL)
{
tail->next = p;
}
if(q!=NULL)
{
tail->next = q;
}
}
4.2 就地逆置问题
//相当于头插法
void ReverseList(LinkList L)
{
Node *p, *q;
p = L->next;
L->next = NULL;
while(p!=NULL)
{
q = p->next;
p->next = L->next;
L->next = p;
p = q;
}
}
5 顺序表和链表的比较
5.1 空间角度
- 顺序表的存储空间是静态分配的,在程序执行之前必须明确规定它的存储规模。适合于线性表长度变化不大,且容易估计长度的情况下。
- 存储密度(Storage Density), 是指结点数据本身所占的存储量和整个结点结构所占的存储量之比。若不考虑顺序表中的备用结点空间,则顺序表的存储空间利用率为100%,而单链表的存储空间利用率为50%
结论:当线性表的长度变化不大, 易于事先确定其大小时,为了节约存储空间,宜采用顺序表作为存储结构。
5.2 时间角度
- 若线性表的操作主要是进行查找,很少做插入和删除时,宜采用顺序表做存储结构。
- 在链表中的任何位置上进行插入和删除,都只需要修改指针。而在顺序表中进行插入和删除,平均要移动表中近一半的结点,尤其是当每个结点的信息量较大时,移动结点的时间开销就相当可观。
- 对于频繁进行插入和删除的线性表,宜采用链表做存储结构。 若表的插入和删除主要发生在表的首尾两端,则宜采用尾指针表示的单循环链表。
5.3 语言角度
- 对于没有提供指针类型的高级语言,若要采用链表结构,则可以使用光标实现的静态链表。虽然静态链表在存储分配上有不足之处,但它和动态链表一样,具有插入和删除方便的特点。
- 对那些具有指针类型的语言,静态链表也有其用武之地。特别是当线性表的长度不变,仅需改变结点之间的相对关系时,静态链表比动态链表可能更方便
6 链式存储方式的比较
7 实验课题目
7.1 链表删除
#include <stdio.h>
#include <stdlib.h>
//单链表的存储结构描述:
typedef struct Node
{
int data;
struct Node * next;
}Node,*LinkList;
//初始化单链表:
void CreatFromTail(LinkList L)
{
struct Node *s;
struct Node *r;
int value;
r = L;
printf("请输入元素值(输入0表示结束输入):");
while(1) {
scanf("%d",&value);
if(value == 0){
return;
}
s = (struct Node *)malloc(sizeof(struct Node));
s->data = value;
r->next = s;
r = s;
r->next = NULL;
}
return;
}
//删除
void DelList(LinkList L, int e){
struct Node *pre;
struct Node *r;
pre = L;
r = L->next;
while(r!=NULL) {
if(r->data == e) {
pre->next = r->next;
}
r = r->next;
pre = pre->next;
}
}
//输出链表
void printList(struct Node *L) {
struct Node *p;
p = L->next;
while(p != NULL) {
printf("%d ",p->data);
p = p->next;
}
return;
}
int main()
{
int x;
struct Node *L;
L = ( struct Node *)malloc(sizeof(struct Node));
L->next = NULL;
CreatFromTail(L);
printf("请输入要删除的元素:");
scanf("%d",&x);
printf("删除前");
printList(L);
printf("\n");
printf("删除后");
DelList(L,x);
printList(L);
}
结果如下:
7.2 链表逆序
#include <stdio.h>
#include <stdlib.h>
//单链表的存储结构描述:
typedef struct Node
{
int data;
struct Node * next;
}Node,*LinkList;
//初始化单链表:
void CreatFromTail(LinkList L)
{
struct Node *s;
struct Node *r;
int value;
r = L;
printf("请输入元素值(输入0表示结束输入):");
while(1) {
scanf("%d",&value);
if(value == 0){
return;
}
s = (struct Node *)malloc(sizeof(struct Node));
s->data = value;
r->next = s;
r = s;
r->next = NULL;
}
return;
}
void ReverseList(LinkList L)
{
Node *p, *q;
p = L->next;
L->next = NULL;
while(p!=NULL)
{
q = p->next;
p->next = L->next;
L->next = p;
p = q;
}
}
//输出链表
void printList(struct Node *L) {
struct Node *p;
p = L->next;
while(p != NULL) {
printf("%d ",p->data);
p = p->next;
}
return;
}
int main()
{
int x;
struct Node *L;
L = ( struct Node *)malloc(sizeof(struct Node));
L->next = NULL;
CreatFromTail(L);
printf("逆序前");
printList(L);
printf("\n");
printf("逆序后");
ReverseList(L);
printList(L);
}
结果如下:
7.3 链表合并
#include <stdio.h>
#include <stdlib.h>
//单链表的存储结构描述:
typedef struct Node
{
int data;
struct Node * next;
}Node,*LinkList;
//初始化单链表:
void CreatFromTail(LinkList L)
{
struct Node *s;
struct Node *r;
int value;
r = L;
printf("请输入元素值(输入0表示结束输入):");
while(1) {
scanf("%d",&value);
if(value == 0){
return;
}
s = (struct Node *)malloc(sizeof(struct Node));
s->data = value;
r->next = s;
r = s;
r->next = NULL;
}
return;
}
//输出链表
void printList(struct Node *L) {
struct Node *p;
p = L->next;
while(p != NULL) {
printf("%d ",p->data);
p = p->next;
}
return;
}
//两个有序的单链表合并
LinkList MergeLinkList(LinkList LA, LinkList LB)
{
Node *pa, *pb, *pc,*r;
LinkList LC;
pa = LA->next;
pb = LB->next;
LC = LA;
LC->next = NULL;
r = LC;
while(pa!=NULL&&pb!=NULL)
{
if(pa->data<=pb->data)
{
r->next = pa;
r = pa;
pa = pa->next;
}
else
{
r->next = pb;
r = pb;
pb = pb->next;
}
}
if(pa)
r->next = pa;
else
r->next = pb;
free(LB);
//去重
pc = LC->next;
while(pc->next!=NULL){
if(pc->next->data == pc->data)
{
pc->next = pc->next->next;
}
pc = pc->next;
}
return(LC);
}
int main()
{
struct Node *la;
struct Node *lb;
struct Node *lc;
la = ( struct Node *)malloc(sizeof(struct Node));
la->next = NULL;
lb = ( struct Node *)malloc(sizeof(struct Node));
lb->next = NULL;
lc = ( struct Node *)malloc(sizeof(struct Node));
lc->next = NULL;
CreatFromTail(la);
CreatFromTail(lb);
printf("合并前la:");
printList(la);
printf(",");
printf("合并前lb:");
printList(lb);
printf("\n");
printf("合并后lc:");
lc = MergeLinkList(la,lb);
printList(lc);
}
结果如下:
7.4 顺序表合并
#include <stdio.h>
#include <stdlib.h>
#define MAX 20
//顺序表的存储结构描述:
typedef struct
{
int elem[MAX]; /*线性表占用的数组空间*/
int last; /*记录线性表中最后一个元素在数组elem[]的位置*/
}SeqList;
//初始化顺序表:
void CreateList(SeqList *L)
{
int value = 0;
int i = 0;
printf("创建顺序表,请输入元素值:");
while(1)
{
scanf("%d",&value);
if(value == 0)
{
break;
}
L->elem[i] = value;
i++;
}
L->last = i;
return;
}
//输出顺序表
void printList(SeqList *L) {
int i=0;
while(i<L->last)
{
printf("%d ",L->elem[i]);
i++;
}
}
void DelList(SeqList *L,int i)
/*
删除在顺序表第i个位置,用e返回其值
*/
{
int k;
if((i<1)||(i>L->last+1))
{
printf("删除位置不合法");
return;
}
//e = L->elem[i-1];
if(L->last >= 20-1)
{
printf("表已满,无法插入");
return;
}
for(k = i; k<= L->last; k++)
{
L->elem[k-1] = L->elem[k];
}
L->last--;
return;
}
//两个有序的顺序表合并:
void MergeLinkList(SeqList *LA, SeqList *LB,SeqList *LC)
{
int i=0;
int j=0;
int k=0;
while(i<=LA->last && j<=LB->last)
{
if(LA->elem[i]<=LB->elem[j])
{
LC->elem[k] = LA->elem[i];
i++;k++;
}
else if(LA->elem[i]>LB->elem[j])
{
LC->elem[k] = LB->elem[j];
j++;k++;
}
}
while(i<LA->last)
{
LC->elem[k]=LA->elem[i];
i++;k++;
}
while(j<LB->last)
{
LC->elem[k]=LB->elem[j];
j++;k++;
}
LC->last = LA->last+LB->last;
//去重
i = 0;
while(i<LC->last){
if(LC->elem[i+1]==LC->elem[i])
{
DelList(LC,i+1);
}
i++;
}
}
int main()
{
SeqList *la;
SeqList *lb;
SeqList *lc;
la = (SeqList *)malloc(sizeof(SeqList));
lb = (SeqList *)malloc(sizeof(SeqList));
lc = (SeqList *)malloc(sizeof(SeqList));
CreateList(la);
CreateList(lb);
printf("合并前la:");
printList(la);
printf(",");
printf("合并前lb:");
printList(lb);
printf("\n");
printf("合并后lc:");
MergeLinkList(la,lb,lc);
printList(lc);
}
结果如下:
7.5 约瑟夫环
题目描述:
#include "stdio.h"
#include "stdlib.h"
//链表结构描述:
typedef struct Node{
int index;
struct Node *next;
} LinkList;
//初始化线性表
LinkList* CreateList(LinkList *L,int n){
LinkList *p;
LinkList *r;
L=(LinkList*)malloc(sizeof(struct Node));
L->next=NULL;
r=L;
for (int i=1; i<=n; i++)
{
p=(LinkList*)malloc(sizeof(struct Node));
p->index=i;
r->next=p;
r=p;
}
r->next=L->next;
L = L->next;
return L;
}
//删除
void DelList(LinkList *L,int n){
LinkList *q;
while(L!=L->next)
{
for (int i = 2; i <n; i++)
{
L=L->next;
}
q=L->next;
L->next=L->next->next;
free(q); //释放节点
L=L->next;
}
printf("最后剩余的猴子是:%d",L->index);
}
int main(){
LinkList *L;
LinkList *Head=NULL;
LinkList *Circle;
int n,m,start;
printf("猴子个数:");
scanf("%d",&n);
printf("设置M为:");
scanf("%d",&m);
Head = CreateList(L,n);
DelList(Head,m);
return 0;
}
结果如下: