5.线性表的链式表示和实现
链式存储结构:结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻。
线性表的链式表示又称为非顺序映像或链式映像
链式存储的特点:
- 用一组物理位置任意的存储单元来存放线性表的数据元素
- 这组存储单元既可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。
- 链表中元素的逻辑次序和物理次序不一定相同。
- 访问时只能通过头指针进入链表,并通过每个节点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花时间不等
这种存取元素的方法被称为顺序存取法
采用链式存储方式存储的线性表称为链表,链表中每一个结点包含存放数据元素的值的数据域和存放指向逻辑上相邻结点的指针域。
链式存储相关术语:
- 结点:数据元素的存储映像。由数据域和指针域两部分组成
- 数据域:存储数据元素信息的域称为数据域
- 指针域:存储直接后继存储位置的域称为指针域
- 链表:n个结点由指针链组成一个链表,它是线性表的链式存储映像,称为线性表的链式存储结构
- 单链表、双链表和循环链表
- 结点只有一个指针域的链表,称为单链表或线性链表
- 结点有两个指针域的链表,称为双链表
- 首尾相接的链表称为循环链表
- 头指针、头节点和首元结点
- 首元结点:是指链表中存储第一个数据元素a1的结点
- 头指针:头指针是指向链表中第一个结点的指针。若链表设有头结点,则头指针所指结点为线性表的头结点;若链表不设头结点,则头指针所指结点为该线性表的首元结点。
- 头结点:是在首元结点之前附设的一个结点,其指针域指向首元结点。头结点的数据域可以不存储任何信息,也可存储与数据元素类型相同的其他附加信息。
链表的存储结构示意图有以下两种形式:
- 不带头结点
- 带头结点
问题一:如何表示空表?
无头结点时,头指针为空时表示空表
有头结点时,当头结点的指针域为空时表示空表
问题二:在链表中设置头结点有什么好处?
链表增加头结点的作用如下:
(1)便于首元结点的处理
增加了头结点后,首元结点的地址保存在头结点的指针域中,则对链表的第一个数据元素的操作与其他数据元素相同,无需进行特殊处理。
(2)便于空表和非空表的统一处理
增加头结点后,无论链表是否为空,头指针都是指向头结点的非空指针。
问题三:头结点的数据域内装的是什么?
头结点的数据域可以为空,也可以存放线性表长度等附加信息,但此节点不能计入链表长度值
存储结构表示
//- - - - - 单链表的存储结构- - - - -
typedef struct Link{
shelfType elem; //结点的数据域
struct Link * next; //结点的指针域
}link;
这里定义的是单链表中每个结点的存储结构,它包括两部分:存储结点的数据域data,存储后继结点位置的指针域next。
1.单链表的初始化
算法步骤:
- 生成新结点作头结点,用头指针L指向头结点。
- 将头结点的指针域置空。
带头结点的空单链表就是一个只含头结点的单链表。所以要创建一个空的单链表,则只要分配一个结点大小的空间来作为头结点,并使其指针域置为空指针,头指针指向该结点即可。
提供
算法描述:
//创建链表
link *createLink(){
//构造一个空的单链表L
link *headNode=(link*) malloc(sizeof (link)); //生成新结点作为头结点,用头指针headNode指向头结点
headNode->next=NULL; //头结点的指针域置空
return headNode;
}
以下两种实现可以好好比对一下,想一想区别和为什么
//链表初始化,无头结点初始化
link * initLink(){
link *p=NULL; //创建头指针
link *temp=(link*)malloc(sizeof (link)); //创建首元结点
//首元结点初始化
temp->elem=1;
p=temp;
for (int i = 1; i < 9; ++i) {
link *a=(link*) malloc(sizeof (link));
a->elem=i;
a->next=NULL;
//将temp结点与新建立的a结点建立逻辑关系
temp->next=a;
temp=temp->next;
}
return p;
}
//链表初始化,带头结点初始化
link *initLinkTou(){
link *p=(link*) malloc(sizeof (link));
link *temp=p;
for (int i = 1; i < 5; ++i) {
link *a=(link*) malloc(sizeof (link));
a->elem=i;
a->next=NULL;
temp->next=a;
temp=temp->next;
}
}
2.单链表的查找
由于单链表的存储空间不连续,所以它不能像顺序表那样直接通过位序号来定位其存储地址,从而实现随机存取。单链表是一种顺序存取的结构,如果要取第i个结点的数据值,只能从头指针所指的结点开始沿着后继指针依次进行查找。
//按位序号查找元素
shelfType locateElem(link *l,int i,shelfType elem){
link *temp=l->next;
int j=1; //记录现结点指针的位置
while (temp&&j<i){ //遍历后续结点
temp=temp->next;
j++;
}
if(!temp||j>i){
printf("位置输入有误!\n");
return -1;
}
elem=temp->elem;
return elem;
}
//按元素值查找链表中元素出现的第一个位置
int findElem(link *l,shelfType elem){
link *p=l->next;
int n=1;
while (p->elem){
if(elem==p->elem){
printf("元素在链表第%d位.",n);
return n;
}
p=p->next;
n++;
}
printf("链表中没有这个元素,请确认!");
return -1;
}
3.单链表的插入值操作
在单链表上进行插入操作的主要步骤归纳如下:
(1)查找到待插入位置的前驱结点。
(2)创建数据域值为x的新结点。
(3)修改相关结点的链指针,使新结点插入到单链表中指定的位置上。
//在带头结点链表中插入元素
link * insertElem(link *l,shelfType elem,int insert){
link *temp=l;
int i=0;
//找到插入位置的上一个结点
//可以思考一下这种方法为什么不行
/*for (int i = 1; i < insert; ++i) {
temp=temp->next;
if(!temp){
printf("插入位置无效\n");
return l;
}
}*/
while (temp&&i<insert-1){
temp=temp->next;
i++;
}
if(!temp||i>insert-1){
printf("插入位置错误!");
return l;
}
//创建插入结点
link *c=(link*) malloc(sizeof (link));
c->elem=elem;
c->next=temp->next;
temp->next=c;
return l;
}
//在不带头结点的链表中插入值
link * insertElem_Nohead(link *l,shelfType elem,int insert){
link *temp=l;
int i=0;
//找到插入位置的上一个结点
while (temp&&i<insert-1){
temp=temp->next;
i++;
}
if(!temp||i>insert-1){
printf("插入位置错误!");
return l;
}
//创建插入结点
link *c=(link*) malloc(sizeof (link));
c->elem=elem;
if(insert==1){
c->next=l;
l=c;
} else{
c->next=temp->next;
temp->next=c;
}
return l;
}
4.单链表上的删除操作
//链表删除某个位置的元素
link * delElem(link *l,int del){
link * temp = l;
int i = 0;
//找到要删除节点的上一个结点
while (temp->next&&l<del-1){ //注意这条语句的判断标准,是和上面不一样的
temp=temp->next;
i++;
}
if(!temp->next||i>del-1){
printf("输入删除位置错误");
return l;
}
link *d=temp->next;
temp->next=temp->next->next;
free(d);
return l;
}
5.单链表的输出操作
由于链式存储是一种顺序存取的结构,所以要输出单链表中各结点的数据元素值,只要从首结点出发,沿着后继指针在对每一个结点进行遍历的过程中将其数据域的值输出即可。
//链表输出
void displayList(link *l){
link *p=l->next;
while (p){
printf("%d\n",p->elem);
p=p->next;
}
}
6.单链表建立操作
1).头插法创建单链表
头插法创建单链表每次都是将新结点插入到当前形成的单链表的表头,其插入过程如图所示。
//头插法创建单链表
link *createList_ByHead(link *l){
printf("请逆位序输入数据值(0结束):");
shelfType node;
scanf("%d",&node);
while (node!=0){
link *p=(link*) malloc(sizeof (link));
if(!p){
printf("分配元素空间出错!");
}
p->elem=node;
p->next=l->next;
l->next=p;
scanf("%d",&node);
}
return l;
}
所有运行代码,大家可以试着自己去实现一些示例
//
// Created by 99514 on 21/12/2.
//
#include <malloc.h>
#include <stdbool.h>
#include "stdio.h"
typedef int shelfType;
typedef struct Link{
shelfType elem;
struct Link * next;
}link;
//链表初始化,无头结点初始化
link * initLink(){
link *p=NULL; //创建头指针
link *temp=(link*)malloc(sizeof (link)); //创建首元结点
//首元结点初始化
temp->elem=1;
p=temp;
for (int i = 1; i < 9; ++i) {
link *a=(link*) malloc(sizeof (link));
a->elem=i;
a->next=NULL;
//将temp结点与新建立的a结点建立逻辑关系
temp->next=a;
temp=temp->next;
}
return p;
}
//链表初始化,带头结点初始化
link *initLinkTou(){
link *p=(link*) malloc(sizeof (link));
link *temp=p;
for (int i = 1; i < 5; ++i) {
link *a=(link*) malloc(sizeof (link));
a->elem=i;
a->next=NULL;
temp->next=a;
temp=temp->next;
}
}
//创建链表
link *createLink(){
link *headNode=(link*) malloc(sizeof (link));
headNode->next=NULL;
return headNode;
}
//创建结点
link *createNode(shelfType data){
link *node=(link*) malloc(sizeof (link));
node->elem=data;
node->next=NULL;
return node;
}
//按元素值查找链表中元素出现的第一个位置
int findElem(link *l,shelfType elem){
link *p=l->next;
int n=1;
while (p->elem){
if(elem==p->elem){
printf("元素在链表第%d位.",n);
return n;
}
p=p->next;
n++;
}
printf("链表中没有这个元素,请确认!");
return -1;
}
//按位序号查找元素
shelfType locateElem(link *l,int i,shelfType elem){
link *temp=l->next;
int j=1; //记录现结点指针的位置
while (temp&&j<i){ //遍历后续结点
temp=temp->next;
j++;
}
if(!temp||j>i){
printf("位置输入有误!\n");
return -1;
}
elem=temp->elem;
return elem;
}
//输出链表中所有的值,带头结点
void display(link *l){
link *p=l;
while (p->next){
p=p->next;
printf("%d\n",p->elem);
}
}
//输出链表中所有的值,不带头结点
void displayNoHead(link *l){
link *p=l;
while (p){
p=p->next;
printf("%d\n",p->elem);
}
}
//链表输出
void displayList(link *l){
link *p=l->next;
while (p){
printf("%d\n",p->elem);
p=p->next;
}
}
//在带头结点链表中插入元素
link * insertElem(link *l,shelfType elem,int insert){
link *temp=l;
int i=0;
//找到插入位置的上一个结点
//可以思考一下这种方法为什么不行
/*for (int i = 1; i < insert; ++i) {
temp=temp->next;
if(!temp){
printf("插入位置无效\n");
return l;
}
}*/
while (temp&&i<insert-1){
temp=temp->next;
i++;
}
if(!temp||i>insert-1){
printf("插入位置错误!");
return l;
}
//创建插入结点
link *c=(link*) malloc(sizeof (link));
c->elem=elem;
c->next=temp->next;
temp->next=c;
return l;
}
//在不带头结点的链表中插入值
link * insertElem_Nohead(link *l,shelfType elem,int insert){
link *temp=l;
int i=0;
//找到插入位置的上一个结点
while (temp&&i<insert-1){
temp=temp->next;
i++;
}
if(!temp||i>insert-1){
printf("插入位置错误!");
return l;
}
//创建插入结点
link *c=(link*) malloc(sizeof (link));
c->elem=elem;
if(insert==1){
c->next=l;
l=c;
} else{
c->next=temp->next;
temp->next=c;
}
return l;
}
//链表删除某个位置的元素
link * delElem(link *l,int del){
link * temp = l;
int i = 0;
//找到要删除节点的上一个结点
while (temp->next&&l<del-1){
temp=temp->next;
i++;
}
if(!temp->next||i>del-1){
printf("输入删除位置错误");
return l;
}
link *d=temp->next;
temp->next=temp->next->next;
free(d);
return l;
}
//要删除链表中的指定元素
link * delPointElem(link *l,shelfType del){
link *temp=l;
int n= findElem(temp,del);
if(n!=-1){
for (int i = 1; i < n; ++i) {
temp=temp->next;
}
link *d=temp->next;
temp->next=temp->next->next;
free(d);
return l;
}
return l;
}
//更新某一类指定元素
link *changePointElem(link *l,shelfType change,shelfType elem){
link *temp=l;
int n=0;
while (temp->next){
temp=temp->next;
if(temp->elem==change){
temp->elem=elem;
n++;
}
}
if(n==0){
printf("链表不存在这个元素");
}
return l;
}
//更新某个位置的元素
link *changeElem(link *l,shelfType elem,int locat){
link *temp=l;
for (int i = 0; i < locat; ++i) {
temp=temp->next;
if(!temp->next){
printf("位置有错误");
return l;
}
}
temp->elem=elem;
return l;
}
//头插法创建单链表
link *createList_ByHead(link *l){
printf("请逆位序输入数据值(0结束):");
shelfType node;
scanf("%d",&node);
while (node!=0){
link *p=(link*) malloc(sizeof (link));
if(!p){
printf("分配元素空间出错!");
}
p->elem=node;
p->next=l->next;
l->next=p;
scanf("%d",&node);
}
return l;
}
//尾插法初始化链表
link *addNode(link *l){
link *temp=l;
printf("你想要多少个结点:");
int n;
scanf("%d",&n);
for (int i = 1; i <= n; ++i) {
temp->next= createNode(i);
temp=temp->next;
}
return temp;
}
//操作选择
void choose(link *temp){
printf("=========================\n");
printf("1:输出链表\n");
printf("2:寻找链表中的值\n");
printf("3:插入值\n");
printf("4:删除值\n");
printf("你想要对这个链表做什么:");
int m;
scanf("%d",&m);
switch (m) {
case 1:{
display(temp);
break;
}
case 2:{
printf("你想要寻找到值:");
int a;
scanf("%d",&a);
int b=findElem(temp,a);
printf("这个值在链表的第%d个\n",b);
break;
}
case 3:{
printf("你想要插入的值:");
int a;
scanf("%d",&a);
int b;
printf("你想要插入的位置:");
scanf("%d",&b);
temp= insertElem(temp,a,b);
break;
}
case 4:{
int a;
printf("你想要删除的位置:");
scanf("%d",&a);
temp= delElem(temp,a);
break;
}
}
}
int main(){
link *p=createLink();
link *temp=p;
temp= addNode(temp);
int n = 1;
do {
choose(p);
n++;
int a;
if(n==5){
printf("想要继续吗:1:继续 2:停止\n");
scanf("%d",&a);
}
switch (a) {
case 1:{
n=1;
break;
}
case 2:{
n=6;
break;
}
}
} while (n<6);
return 0;
}