文章目录
1.数据结构的三要素
对任何一个数据结构我们都应该思考这三要素具体指的是什么,以便进行相关操作,例如对于顺序表来说,它是一个“完整的数据结构”,逻辑结构是线性结构、存储结构是顺序存储以及施加在其上的运算,而如果单单说栈,只能表示其逻辑结构,至于存储结构不确定,可用顺序存储或者链式存储。
2.线性表的定义及存储结构
2.1何为线性表?
定义:线性表是具有相同数据类型的n(n>=0)个数据元素的有限序列 ,n为表长,n=0时为空表。
注意:线性表只是一种逻辑结构,表示元素一对一之间的关系,顺序表和链表指的是存储结构,二者属于不同层面的概念,不要混淆。
2.2线性表的两类存储表示
2.2.1顺序存储表示
线性表顺序存储表示又称顺序表,它用一组地址连续的存储单元依次存储线性表中的数据元素,使得逻辑相邻的两个元素在物理位置上也相邻,由于各元素连续存储在数组中,所以属于随机存取、顺序存储类型,存储密度较高(每个结点只存储数据元素),插入删除时需要移动大量元素。
#define maxsize 100
//静态分配一维数组的顺序表,数组大小和空间已固定,一旦空间占满有着程序崩溃危险
typedef struct
{
ElemType data[maxsize];
int length;
}sqlist;
//动态分配一维数组的顺序表,用的时候自由分配数组大小,用完了可以重新分配更大的空间替换原空间
typedef struct
{
ElemType *data;
int length, maxsize;//length为当前个数;maxsize为最大容量(一般不用管)
}sqlist;
2.2.2链式存储表示
线性表链式存储表示又称链表,用一个个结点将各数据元素相连,不要求逻辑上相邻的元素在物理位置上也相邻,存储单元不连续,由于每个结点中有指针域,因此在进行插入和删除时只需修改指针域即可,无需移动大量元素,当然缺点就是失去了随机存取的特性,属于顺序存取,随机存储类型。
3.顺序表
3.1顺序表操作汇总
3.1.1初始化顺序表
void initlist(sqList *l)//初始化顺序表,形参也可以写sqList &l,但需要在函数声明处参数改为sqList &
{
l->length = 0;
/*
l->data = (int *)malloc(sizeof(int)*len);//动态分配一段连续空间,并将此段空间首地址给data,好处在于用多少分配多少空间,不用一上来就分配个最大空间
l.length = len;
*/
printf("初始化成功!\n");
}
3.1.2创建顺序表
void create(sqList *l)//创建顺序表,让用户对顺序表各个元素赋值
{
int i = 0, leng = 0;
printf("请输入顺序表表长:\n");
scanf("%d", &leng);
l->length = leng;
printf("依次输入这%d个元素:\n", leng);
for (i; i < leng; i++)
{
scanf("%d", &l->data[i]);
}
printf("顺序表创建完成!\n");
}
3.1.3求表长
int length(sqList l)//返回当前顺序表表长
{
return l.length;
}
3.1.4按值查找操作
int LocateElem(sqList l, int e)//查找顺序表中是否存在e值,存在返回其下标,否则返回-1
{
int i = 0;
for (i; i < l.length; i++)
{
if (l.data[i] == e)
{
return i+1;
}
}
return -1;
}
//平均时间复杂度:O(n)
3.1.5按位查找操作
int GetElem(sqList l, int i)//返回顺序表第i位置上的元素,注意:i此时非下标
{
int j;
if (i<1 || i>l.length)//判断位置是否合法
{
printf("位置有误!!\n");
return -1;
}
for ( j = 0; j != i - 1 && j < l.length; j++);
return l.data[j];
}
//平均时间复杂度:O(1)
3.1.6删除操作
void ListDelete(sqList *l, int i, int &e)//删除第i个位置上的元素并用e存储被删除元素
{
if (i<1 || i>l->length)//判断位置是否合法
{
printf("位置有误!!\n");
return;
}
e = l->data[i - 1];
for (int j = i; j < l->length; j++)//被删除元素后的所有元素依次向前移动
{
l->data[j - 1] = l->data[j];
}
l->length--;
printf("删除成功!\n");
}
//平均时间复杂度:O(n)
3.1.7插入操作
void ListInsert(sqList *l, int i, int e)//往顺序表第i个位置插入元素e
{
if (i<1 || i>l->length + 1)//非法位置
{
printf("插入位置有误!!\n");
return;
}
if (l->length == maxsize)//顺序表已到最大长度,无法插入
{
printf("顺序表已到最大长度,无法插入\n");
return;
}
for (int j = l->length-1; j >=i-1; j--)
{
l->data[j + 1] = l->data[j];
}
l->length++;
l->data[i - 1] = e;
printf("插入成功!\n");
}
//平均时间复杂度:O(n)
3.1.8遍历操作
void PrintList(sqList l)//遍历顺序表
{
printf("当前顺序表为:\n");
for (int i = 0; i < l.length; i++)
{
printf("%d ", l.data[i]);
}
putchar('\n');
}
3.1.9判空操作
bool Empty(sqList l)//判断是否为空表
{
if (l.length == 0)
return true;
else
return false;
}
3.1.10销毁操作
void DestoryList(sqList *l)//销毁顺序表,针对于顺序表结构中一维数组是动态分配的情况
{
free(l->data);
}
主函数
#include<stdio.h>
#include<stdlib.h>
#define maxsize 50
typedef struct
{
int length;//顺序表实际长度
int data[maxsize];//顺序表的元素
}sqList;
int main()
{
sqList L;//定义一个顺序表,已为其分配了空间
int e, i, service, flag = 1, len = 0;
void initlist(sqList *);
void create(sqList *);
int length(sqList );
int LocateElem(sqList ,int);
int GetElem(sqList,int);
void ListDelete(sqList *,int,int&);
void ListInsert(sqList *,int,int);
void PrintList(sqList);
bool Empty(sqList);
void DestoryList(sqList *);
while (flag)
{
printf("请输入操作序号:\n");
printf("\t1.初始化顺序表\t\t2.创建顺序表\n");
printf("\t3.求表长\t\t4.按值查找元素\n");
printf("\t5.获取某个位置上的元素\t\t6.删除某位置元素\n");
printf("\t7.在某位置上插入某元素\t\t8.输出当前顺序表\n");
printf("\t9.当前顺序表是否为空\t\t10.销毁顺序表\n");
scanf("%d", &service);
switch (service)
{
case 1:
initlist(&L);
break;
case 2:
create(&L);
break;
case 3:
len = length(L);
printf("表长为%d\n", len);
break;
case 4:
printf("输入你要查找的值:\n");
scanf("%d", &e);
if (LocateElem(L, e) != -1)
printf("此值是第%d个元素\n", LocateElem(L, e));
else
printf("没有这个值\n");
break;
case 5:
printf("你要查找第几个位置上的值?\n");
scanf("%d", &i);
if (GetElem(L, i) != -1)
{
printf("%d位置上的值为%d\n", i, GetElem(L, i));
}
break;
case 6:
printf("你要删除第几个位置上的元素?\n");
scanf("%d", &i);
ListDelete(&L, i, e);
break;
case 7:
printf("请依次输入插入位置及值:\n");
scanf("%d%d", &i, &e);
ListInsert(&L, i, e);
break;
case 8:
PrintList(L);
break;
case 9:
if (Empty(L))
printf("当前为空!\n");
else
printf("当前不为空!\n");
break;
case 10:
DestoryList(&L);
break;
default:
break;
}
printf("继续操作输入1否则输入0:\n");
scanf("%d", &flag);
}
return 0;
}
运行结果
请输入操作序号:
1.初始化顺序表 2.创建顺序表
3.求表长 4.按值查找元素
5.获取某个位置上的元素 6.删除某位置元素
7.在某位置上插入某元素 8.输出当前顺序表
9.当前顺序表是否为空 10.销毁顺序表
3.2练习
3.2.1逆置问题
问题描述:将一个数组中的各元素按照用户的各种需求进行逆置,如以下三种情况
- 将一长度为n的数组的前端k(k<n)个元素逆序后移到数组后端,要求原数组中数据不丢失,其余位置元素无关紧要,如13579变为57931。
- 将一长度为n的数组的前端k(k<n)个元素保持原序移到数组后端,要求原数组中数据不丢失,其余位置元素无关紧要,如13579变为57913。
- 将数组中元素(X0,X1,…,Xn-1),经过移动后变为(Xp,Xp+1,…,Xn-1,X0,X1,…,Xp-1),即循环左移p(0<p<n)个位置,如13579变为57913。
思路:其实上述三类问题属于同一类问题,可以用一个通用函数(reverse)依次解决,只需改变此函数的形参即可。
- 问题1解法:直接逆置整个数据即可。
- 问题2解法:先逆置前k个元素,在逆置整个数组即可。
- 问题3解法:先将0~p-1位置元素逆置,在逆置p—n-1位置元素,最后逆置整个数组即可
void reverse(int *a, int left, int right)//形参含义:将数组a从下标left到right的k个元素逆置
{
int temp;
for (int i = left, j = right; i<j; ++i, --j)
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
问题三实现
#include<stdio.h>
int main()
{
int a[8] = { 1,3,65,4,2,15,5,98 };
void reverse(int *, int, int);
reverse(a, 0, 4);
reverse(a, 5, 7);
reverse(a, 0, 7);
for (int i = 0; i < 8; i++)
printf("%d ", a[i]);
return 0;
}
void reverse(int *a, int left, int right)//形参含义:将数组a从下标left到right的k个元素逆置
{
int temp;
for (int i = left, j = right; i<j; ++i, --j)
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
运行结果
15 5 98 1 3 65 4 2
4.链表
4.1何为单链表
通过一组任意的存储单元来存储线性表中的数据元素,为了建立元素间线性关系,对每个链表结点定义两个域——指针域和数据域,数据域用来存放数据,指针域用来指向其后继结点,由于单链表的元素随机离散的分布在存储空间,所以是非随机存取的存储结构,即不能直接找到某特定顶点,需要从表头遍历查找 ,空表条件是头指针l->next等于NULL,结点结构如下
typedef struct node
{
int data;
struct node *next;
}lnode,*linklist;//linklist p等价于lnode *p
4.2单链表操作
4.2.1头插法建立单链表
void headcreate(linklist &L)//头插法建立单链表
{
int x;
lnode *newnode = NULL;//接收新结点
L = (linklist)malloc(sizeof(lnode));//分配头结点空间并让L指向它,与下面一行统称为初始化操作
L->data = 999;//令头结点数据域为999
L->next = NULL;
printf("请依次输入你想要创建的结点的值(9999代表结束):\n");
scanf("%d", &x);
while (x != 9999)
{
newnode = (linklist)malloc(sizeof(lnode));
newnode->data = x;
newnode->next = L->next;
L->next = newnode;
scanf("%d", &x);
}
printf("创建完毕!!\n");
}
//平均时间复杂度:O(n)
4.2.2尾插法建立单链表
void trailcreate(linklist &L)//尾插法建立单链表
{
int x;
lnode *newnode = NULL;
L = (linklist)malloc(sizeof(lnode));//分配头结点空间并让L指向它,与下面一行统称为初始化操作
lnode *r = L;//指向链表尾
L->data = 999;
L->next = NULL;
printf("请依次输入结点值(9999代表结束):\n");
scanf("%d", &x);
while (x!=9999)
{
newnode = (linklist)malloc(sizeof(lnode));
newnode->data = x;
r->next = newnode;
newnode->next = NULL;
r = newnode;
scanf("%d", &x);
}
printf("创建完毕!!\n");
}
4.2.3返回某位置i的元素的指针
lnode *GetElem(linklist L, int i)//返回指向链表第i位置上的指针
{
int count = 1;//记录当前遍历的顶点是第几个顶点
linklist p = L->next;
if (i == 0)//记住:头结点位置可以当做0位置,可以返回头结点的位置的话这样在后面如果插入函数中要在1位置插入元素就可以用此函数来找前驱,但其实若不是为了插入和删除服务的话,其实0位置上并没什么意义
return L;
if (i < 1)
return NULL;//位置过小导致异常
while (p&&count<i)
{
p=p->next;
count++;
}
return p;
}
//平均时间复杂度:O(n)
4.2.4返回某元素e的指针
lnode *LocateElem(linklist L, int e)//查找值为e的元素,若存在返回其指针
{
lnode *p = L->next;
while (p&&p->data!=e)
p=p->next;
return p;
}
//平均时间复杂度:O(n)
4.2.5插入元素
void Insert(linklist L, int i, int e)//在链表第i位置上插入元素e,此函数需要留心,B站上王老师的那种方法也要理解
{
lnode *p = L->next;
lnode *pre = L;
lnode *newnode = NULL;
newnode = (linklist)malloc(sizeof(lnode));
newnode->next = NULL;
int count = 1;
while (p&&count < i)
{
pre = p;
p = p->next;
count++;
}
if (count == i)
{
newnode->data = e;
newnode->next = pre->next;
pre->next = newnode;
printf("插入完毕!\n");
}
else
printf("插入位置非法!\n");
}
4.2.6删除元素
void DeleteElem(linklist L, int i, int &e)//删除第i个位置上的结点,并将其数据存进e,此函数需要留心
{
lnode *GetElem(linklist , int );//利用前面编好的函数
linklist q = NULL;
linklist pre = GetElem(L, i - 1);//返回第i个结点的前驱结点指针
if (pre != NULL)
{
q = pre->next;//q存放要删除的结点
e = q->data;
pre->next = q->next;
free(q);
printf("第%d位置上的元素%d已成功删除!\n", i, e);
}
else
{
printf("位置有误!\n");
}
}
4.2.7求表长
int Getlength(linklist L)//获取表长
{
linklist p = L->next;
int count = 1;
while (p->next != NULL)
{
count++;
p = p->next;
}
return count;
}
4.2.8遍历
void Print(linklist L)//遍历函数
{
linklist p = L->next;
while (p)
{
printf("%d ", p->data);
p = p->next;
}
printf("遍历完毕!\n");
}
主函数
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
int data;
struct node *next;
}lnode,*linklist;//linklist p等价于lnode *p
int main()
{
int i, e, num, flag = 1;
linklist p = NULL;//p作为中间变量使用,若不在此处定义,改为在使用的时候在定义会出现“p的初始化操作由case标签跳过”的错误
linklist l;//此时l只是一个没有指向的指针
void headcreate(linklist &);
void trailcreate(linklist &);
lnode *GetElem(linklist, int);
lnode *LocateElem(linklist, int);
void Insert(linklist , int, int);
void DeleteElem(linklist , int, int &);//切记:引用型变量需要在函数声明处加上&,引用的变量是什么类型就在其类型后加&
int Getlength(linklist);
void Print(linklist );
while (flag)
{
printf("\t1.头插法建立单链表\t\t2.尾插法建立单链表\n");
printf("\t3.获取第i个结点的指针\t\t4.获取值为e的结点的指针\n");
printf("\t5.插入新的节点\t\t6.删除结点\n");
printf("\t7.表长\t\t8.遍历链表\n");
printf("\t\t\t\t9.结束\n");
printf("请输入需要的操作序号:\n");
scanf("%d", &num);
switch (num)
{
case 1:
headcreate(l);
break;
case 2:
trailcreate(l);
break;
case 3:
printf("请您输入位置:\n");
scanf("%d", &i);
p = GetElem(l,i);
if (p == NULL)
printf("位置有误!\n");
else
printf("第%d个位置上的值为%d\n", i, p->data);
break;
case 4:
printf("请输入需要获取的值:\n");
scanf("%d", &e);
p=LocateElem(l, e);
if (p)
printf("返回值为%d的指针成功!!!\n", e);
else
printf("没有这个值!!!\n");
break;
case 5:
printf("请输入要插入的位置和值:\n");
scanf("%d%d", &i, &e);
Insert(l, i, e);
break;
case 6:
printf("请输入你要删除的位置:\n");
scanf("%d", &i);
DeleteElem(l, i, e);
break;
case 7:
num = Getlength(l);
printf("表长为%d\n", num);
break;
case 8:
Print(l);
break;
case 9:
exit(0);
break;
}
printf("是否继续?\n");
scanf("%d", &flag);
}
return 0;
}
运行结果
1.头插法建立单链表 2.尾插法建立单链表
3.获取第i个结点的指针 4.获取值为e的结点的指针
5.插入新的节点 6.删除结点
7.表长 8.遍历链表
9.结束
请输入需要的操作序号:
4.3练习题
4.3.1归并问题
问题描述:A和B是两个带头结点的单链表,元素递增有序,设计算法将A和B归并 成一个按元素非递减有序的链表C,C有A和B中结点组成。
思路:所谓非递减就是类似1、2、2、3类似的序列,不严格要求n项大于n-1项(可等于),其次需注意假如A={10,11,12},B={1,3,5},我们是不是可以直接判断由于A的第一个元素大于B的最后一个元素,因而直接将B的最后一个元素与A相连?答案是:不可以,原因是有归并二字的出现,标准的归并算法如下,可以看出若按先前想法则会多一步归并中没有的判断操作。
void merge(linklist A, linklist B, linklist &C)
{
lnode *p = A->next;//p和q分别指向两链表首元结点
lnode *q = B->next;
C = A;//截取A的头结点作为新链表C的头结点
C->next = NULL;
lnode *r = C;//r始终指向C的终端节点,以便插入新结点
free(B);//B的头结点已经无用
while (p != NULL && q != NULL)
{
if (p->data < q->data)
{
r->next = p;
p = p->next;
r = r->next;
}
else
{
r->next = q;
q = q->next;
r = r->next;
}
}
//A和B总有一个先检查完,将剩下的那个链表剩余部分直接接在C的尾部即可
if (p != NULL)
r->next = p;
if (q != NULL)
r->next = q;
}
4.3何为双链表
单链表结点中只有一个指向其后继的指针,使得单链表只能从头结点依次顺序向后遍历,访问前驱结点的时间复杂度为O(n),为此出现了双链表,链表结点有两个指针———prior和next,前者指向前驱结点,后者指向后继结点,空表的条件是头指针l->next等于NULL
typedef struct DNode
{
ElemType data;
struct DNode *next, *prior;
}DNode, *DLinklist;
4.4双链表操作
4.4.1尾插法建立双链表
void trailcreate(DLinklist &l)
{
int now(DLinklist);
int i;
DNode *r = NULL, *newnode = NULL;
l = (DLinklist)malloc(sizeof(DNode));
l->next = l->prior = NULL;
r = l;
printf("请输入各节点的值(9999代表结束):\n");
scanf("%d", &i);
while (i != 9999)
{
newnode = (DLinklist)malloc(sizeof(DNode));
newnode->data = i;
newnode->prior = r;
r->next = newnode;
newnode->next = NULL;
r = r->next;
scanf("%d", &i);
}
printf("创建完毕!\n");
printf("链表当前结点数为:%d\n", now(l));
}
4.4.2头插法建立双链表
void headcreate(DLinklist &l)
{
int now(DLinklist);
int i, flag = 0;//flag用于特殊处理用户输入的第一个结点
DLinklist newnode = NULL;
l = (DLinklist)malloc(sizeof(DNode));
l->next = l->prior = NULL;
printf("请输入各个结点的值(9999代表结束):\n");
scanf("%d", &i);
flag = 1;
while (i != 9999)
{
newnode = (DLinklist)malloc(sizeof(DNode));
newnode->next = NULL;
newnode->data = i;
if (flag)//对第一个插入的结点特殊处理
{
newnode->next = NULL;
l->next = newnode;
newnode->prior = l;
flag = 0;
}
else
{
newnode->next = l->next;
l->next->prior = newnode;
l->next = newnode;
newnode->prior = l;
}
scanf("%d", &i);
}
printf("建立成功!\n");
printf("链表当前结点数为:%d\n", now(l));
}
4.4.3按值查找结点
DNode *findnode(DLinklist l, int x)
{
DLinklist p = l->next;
while (p != NULL)//循环条件中建议不要写p->一类的语句,容易造成野指针
{
if (p->data != x)
p = p->next;
}
return p;
}
4.4.4插入结点☆
void insert(DLinklist l, int i, int e)
{
int now(DLinklist);
DLinklist p = l->next;
DLinklist trail = NULL;
DLinklist newnode = (DLinklist)malloc(sizeof(DNode));
newnode->data = e;
newnode->next = newnode->prior = NULL;
int count = 1;
if (i <= 0)
{
printf("位置非法!\n");
return;
}
while (count < i&&p)
{
p = p->next;
count++;
if (p)//以下2行都是为了用户如果要求在n+1位置插入元素而做的特殊处理,原因是此链表插入元素的原理是找到第i个元素后进行操作,而i+1元素自然找不到
{
if (p->next == NULL && count != i)
trail = p;
}
}
if (count == i&&trail!=NULL)
{
trail->next = newnode;
newnode->prior = trail;
newnode->next = NULL;
}
else if(count == i)
{
newnode->next = p;
p->prior->next = newnode;
newnode->prior = p->prior;
p->prior = newnode;
}
else
{
printf("位置非法!\n");
}
printf("链表当前结点数为:%d\n", now(l));
}
代码解释:插入的合法位置为[1,n+1],由于双链表具有可以快速找到结点前驱的特性,因此对于插入操作,用户想在某个位置插入只需找到此位置结点即可,无需向单链表一样找到i-1个结点,但是由于双链表的插入操作是要涉及对当前结点的前驱进行操作,但是由于通过第n+1结点无法找到其前驱,所以要对其进行特殊处理,具体做法是引入trail指针保存尾结点.
4.4.5删除结点(有点错误)☆
void deleteelem(DLinklist l, int i)
{
int now(DLinklist);
DLinklist p = l->next;
DLinklist s = NULL;
DLinklist trail = NULL;
int count = 1;
if (i <= 0)
{
printf("位置非法!\n");
return;
}
while (i > count && p)
{
count++;
p = p->next;
if (p)
{
if (p->next == NULL)
trail = p;
}
}
if (count == i && trail != NULL&&p)//特殊处理i等于n时,p!=NULL主要作用是为了i排除大于n的情况,因为trail是从count==n时开始被赋值的,后面就一直有值,不加这一句的话后面会出现野指针
{
s = trail;
trail->prior->next = NULL;
printf("删除成功!\n");
}
else if (p&&i == count)//处理用户输入的i为1~n-1位置时,同样也要排除i>n的情况
{
s = p;
p->prior->next = s->next;
s->next->prior = s->prior;
free(s);
printf("删除成功!\n");
}
else
printf("位置非法!\n");
printf("链表当前结点数为:%d\n", now(l));
}
代码解释:不同于插入操作,删除某一个结点需要对其前驱和后继都进行操作,所以同时拥有前驱和后继的属于一种情况([1-n-1]位置),但是n位置无后继所以需要特殊处理。
对于删除和插入的小结:写代码时需要注意哪种情况归为一类,哪种情况归为另一类,插入算法中,通过某结点可以找到其前驱的归为一类([1,n]位置),无法找到其前驱的归为一类进行特殊处理(n+1位置时);删除算法中可以同时找到前驱和后继的属于同一类([1,n-1]位置),无法同时找到前驱和后继的归为一类进行特殊处理(n位置)。
4.5何为单循环链表
链表尾结点的指针域不再为NULL,而是指向 头结点,从而形成一个环,相比于普通单链表,单循环链表的最大改变在于 插入和删除操作,普通单链表中我们可以通过指针是否为NULL来判断是否扫描到链表尾,依次来结束循环,但是由于单循环链表中不存在指针为NULL的情况,所以就需要设置一个标志(flag),当flag为1时表明已经扫描过链表一遍了,无需再次重复扫描,空表条件是头指针l->next等于l。
4.6单循环链表操作
4.6.1头插法建立单循环链表
void headcreate1(linklist & l)
{
int now1(linklist l);
int x;
int flag = 0;//不同1
lnode *r = NULL;//不同2:r始终指向尾结点
lnode *newnode = NULL;//接收新结点
l = (linklist)malloc(sizeof(lnode));//分配头结点空间并让L指向它,与下面一行统称为初始化操作
l->data = 999;//令头结点数据域为999
l->next = l;//不同3:
printf("请依次输入你想要创建的结点的值(9999代表结束):\n");
scanf("%d", &x);
while (x != 9999)
{
newnode = (linklist)malloc(sizeof(lnode));
newnode->data = x;
newnode->next = l->next;
l->next = newnode;
if (!flag)//不同4:插入第一个节点后此结点永远变成尾结点了,让r指向它,最后构成循环做准备
{
r = newnode;
flag = 1;
}
scanf("%d", &x);
}
if (r != NULL)//处理用户直接输入9999的情况
{
r->next = l;//不同:5:尾结点next域指向头结点构成循环单链表
}
printf("创建完毕!!\n");
printf("当前链表剩余节点个数:%d个\n", now1(l));
}
4.6.2尾插法建立单循环链表
void trailcreate1(linklist &l)
{
int now1(linklist l);
int x;
lnode *newnode = NULL;
l = (linklist)malloc(sizeof(lnode));//分配头结点空间并让L指向它,与下面一行统称为初始化操作
lnode *r = l;//指向链表尾
l->data = 999;
l->next = NULL;
printf("请依次输入结点值(9999代表结束):\n");
scanf("%d", &x);
while (x != 9999)
{
newnode = (linklist)malloc(sizeof(lnode));
newnode->data = x;
r->next = newnode;
newnode->next = NULL;
r = newnode;
scanf("%d", &x);
}
if(r!=NULL)//处理用户直接输入9999的情况
r->next = l;//不同点1
printf("创建完毕!!\n");
printf("当前链表剩余节点个数:%d个\n", now1(l));
}
4.6.3删除指定位置元素(☆)
void deleteelem1(linklist l, int i)
{
int flag = 0;
int now1(linklist l);
linklist pre = l, p = l->next;
linklist q = NULL;
int count = 1;
if (i <= 0)
{
printf("位置非法!\n");
return;
}
while (i > count && !flag)//p->next!=l用来防止对链表进行了二次扫描、二次计数,正常情况应该是检查到表尾若还没有这个位置就该退出了
{
count++;
pre = p;
p = p->next;
if (p == l)//与insert1作对比,这里用的是p是否等于l,就不会把n+1位置也当做正常情况了
flag = 1;
}
if (i == count&&!flag)
{
q = p;
pre->next = p->next;
free(q);
printf("已被删除!\n");
}
else
printf("位置非法!\n");
printf("当前链表剩余节点个数:%d个\n", now1(l));
}
4.6.4在指定位置插入元素(☆)
void insert1(linklist l, int i, int e)
{
int now1(linklist l);
int flag = 0;//用来标记是否已经走完一遍链表,防止重复查找
lnode *p = l->next;//p指向首元结点
lnode *pre = l;//指向p的前驱结点
lnode *newnode = NULL;
newnode = (linklist)malloc(sizeof(lnode));
newnode->next = NULL;
int count = 1;
if (i <= 0)
{
printf("位置非法!\n");
return;
}
while (count < i&&!flag)//由于循环链表没有指针域为NULL的结点,所以以flag为0代表一遍链表还没走完
{
pre = p;
if (pre == l)//当pre都指向l了就证明链表已经扫描到尾部了,后面的位置都是非法位置,这里用pre==l可以把i为n+1位置同样处理了
flag = 1;
p = p->next;
count++;
}
if (count == i&&!flag)//必须保证没有重复查找
{
newnode->data = e;
newnode->next = pre->next;
pre->next = newnode;
if (newnode->next == NULL)//不同点1:若是在n+1位置插入元素则需特殊处理以保持循环特性
{
newnode->next = l;
}
printf("插入完毕!\n");
}
else
{
printf("插入位置非法!\n");
}
printf("当前链表剩余节点个数:%d个\n", now1(l));
}
4.6.5输出
void print1(linklist l)
{
int now1(linklist l);
linklist p = l->next;
while (p != l)
{
printf("%d ", p->data);
p = p->next;
}
printf("遍历结束\n");
printf("当前链表剩余节点个数:%d个\n", now1(l));
}
4.7何为双循环链表
顾名思义,它的基础是双链表,只需让尾结点next域指向头结点,头结点prior域指向尾结点即可,空表的条件是p->prior和p->next全为L时。
4.8双循环链表的操作
4.8.1头插法建立双循环链表
void headcreate2(Dlinklist &l)
{
int now2(Dlinklist l);
int i, flag = 0;//flag用于特殊处理用户输入的第一个结点
Dlinklist newnode = NULL;
l = (Dlinklist)malloc(sizeof(Dnode));
l->next = l->prior = l;//不同1:链表为空条件改变
Dlinklist r = NULL;//不同2:r始终指向尾结点
printf("请输入各个结点的值(9999代表结束):\n");
scanf("%d", &i);
flag = 1;
while (i != 9999)
{
newnode = (Dlinklist)malloc(sizeof(Dnode));
newnode->next = NULL;
newnode->data = i;
if (flag)//对第一个插入的结点特殊处理
{
newnode->next = NULL;
l->next = newnode;
newnode->prior = l;
r = newnode;//不同3:第一个被插入的结点永远变成尾结点,让r指向它
flag = 0;
}
else
{
newnode->next = l->next;
l->next->prior = newnode;
l->next = newnode;
newnode->prior = l;
}
scanf("%d", &i);
}
//构成循环双链表
if (r != NULL)//处理用户直接输入9999的情况
{
r->next = l;//不同4
l->prior = r;//不同5
}
printf("建立成功!\n");
printf("当前链表剩余节点个数:%d个\n", now2(l));
}
4.8.2尾插法建立双循环链表
void trailcreate2(Dlinklist &l)
{
int now2(Dlinklist l);
int i;
Dnode *r = NULL, *newnode = NULL;
l = (Dlinklist)malloc(sizeof(Dnode));
l->next = l->prior = NULL;
r = l;
printf("请输入各节点的值(9999代表结束):\n");
scanf("%d", &i);
while (i != 9999)
{
newnode = (Dlinklist)malloc(sizeof(Dnode));
newnode->data = i;
newnode->prior = r;
r->next = newnode;
newnode->next = NULL;
r = r->next;
scanf("%d", &i);
}
if (r != NULL)//不同点1;处理用户直接输入9999的情况
{
r->next = l;
l->prior = r;
}
printf("创建完毕!\n");
printf("当前链表剩余节点个数:%d个\n", now2(l));
}
4.8.3删除指定位置元素(☆)
void deleteelem2(Dlinklist l, int i)
{
int flag = 0;
int now2(Dlinklist l);
Dlinklist p = l->next;
Dlinklist s = NULL;
int count = 1;
if (i <= 0)
{
printf("非法位置!\n");
return;
}
while (i > count&&!flag)
{
count++;
p = p->next;
if (p == l)//p==l证明此后的位置都是非法位置
flag = 1;
}
if (count == i&&!flag)//删除位置也不能为n+1
{
s = p;
p->prior->next = p->next;
p->next->prior = p->prior;
}
else
printf("位置非法!\n");
printf("当前链表剩余节点个数:%d个\n", now2(l));
}
4.8.4在指定位置插入元素(☆)
void insert2(Dlinklist l, int i, int e)
{
int flag = 0;
int now2(Dlinklist l);
Dlinklist p = l->next;
Dlinklist newnode = (Dlinklist)malloc(sizeof(Dnode));
newnode->next = newnode->prior = NULL;
newnode->data = e;
int count = 1;
if (i <= 0)
{
printf("位置非法!\n");
return;
}
while (count <i && !flag)
{
count++;
p = p->next;
if (p == l)//这一步结束后刚好可以用来处理i等于n+1的情况,大于n+1的情况也不可能出现了
flag = 1;
}
if (count==i)
{
newnode->next = p;
p->prior->next = newnode;
newnode->prior = p->prior;
p->prior = newnode;
}
else
{
printf("位置非法!\n");
}
printf("当前链表剩余节点个数:%d个\n", now2(l));
}
4.8.5输出
void print2(Dlinklist l)
{
int now2(Dlinklist l);
Dlinklist p = l->next;
while (p != l)
{
printf("%d ", p->data);
p = p->next;
}
printf("遍历结束\n");
printf("当前链表剩余节点个数:%d个\n", now2(l));
}
主函数
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
int data;
struct node *next;
}lnode,*linklist;
typedef struct Dnode
{
int data;
struct Dnode *next;
struct Dnode *prior;
}Dnode, *Dlinklist;
int main()
{
int flag = 1, i;
int j, e;
linklist p = NULL;
Dlinklist q = NULL;
void headcreate1(linklist &);
void headcreate2(Dlinklist &);
void trailcreate1(linklist &);
void trailcreate2(Dlinklist &);
void insert1(linklist, int, int);
void insert2(Dlinklist, int, int);
void deleteelem1(linklist, int);
void deleteelem2(Dlinklist, int);
bool Empty1(linklist);
bool Empty2(Dlinklist);
void print1(linklist);
void print2(Dlinklist);
while (flag)
{
printf("\t1.头插法建立单循环链表\t\t2.头插法建立双循环链表\n");
printf("\t3.尾插法建立单循环链表\t\t4.尾插法建立双循环链表\n");
printf("\t5.往单循环链表中插入元素\t6.往双循环链表中插入元素\n");
printf("\t7.删除单循环链表中某元素\t8.删除双循环链表中某元素\n");
printf("\t9.判断单循环链表是否为空\t10.判断双循环链表是否为空\n");
printf("\t11.输出单循环链表\t\t12.输出双循环链表\n");
printf("\t\t\t\t13.退出程序\n");
printf("请输入操作序号:\n");
scanf("%d", &i);
switch (i)
{
case 1:
headcreate1(p);
break;
case 2:
headcreate2(q);
break;
case 3:
trailcreate1(p);
break;
case 4:
trailcreate2(q);
break;
case 5:
printf("请输入插入元素的位置及值:\n");
scanf("%d%d", &j, &e);
insert1(p, j, e);
break;
case 6:
printf("请输入插入元素的位置及值:\n");
scanf("%d%d", &j, &e);
insert2(q, j, e);
break;
case 7:
printf("请输入删除位置:\n");
scanf("%d", &j);
deleteelem1(p, j);
break;
case 8:
printf("请输入删除位置:\n");
scanf("%d", &j);
deleteelem2(q, j);
break;
case 9:
Empty1(p) == false ? printf("当前是非空表\n") : printf("当前是空表\n");
break;
case 10:
Empty2(q) == false ? printf("当前是非空表\n") : printf("当前是空表\n");
break;
case 11:
print1(p);
break;
case 12:
print2(q);
break;
case 13:
exit(0);
break;
}
printf("是否继续?\n");
scanf("%d", &flag);
}
}
运行结果
1.头插法建立单循环链表 2.头插法建立双循环链表
3.尾插法建立单循环链表 4.尾插法建立双循环链表
5.往单循环链表中插入元素 6.往双循环链表中插入元素
7.删除单循环链表中某元素 8.删除双循环链表中某元素
9.判断单循环链表是否为空 10.判断双循环链表是否为空
11.输出单循环链表 12.输出双循环链表
13.退出程序
请输入操作序号:
4.9何为静态链表
静态数组借助链表来描述线性表的链式存储结构,结点还是数据域和指针域,区别于普通链表的是,静态链表结点中指针域不在是一个指针变量,而是一个int型变量,它指向后继结点在数组中的下标,规定头结点存在数组a[0]位置,结点的数据域为空,指针域自然指向首元结点在数组中的下标,结点结构如下:
#define maxsize 50
typedef struct
{
int data;
int next;
}SLinkList[maxsize];//主函数中SLinkList p等价于SLinkList p[maxsize]