线性表的链式存储又称单链表,它是指通过一组任意的存储单元来存储线性表的数据元素。
用代码定义一个单链表
typedef struct LNode{ //定义单链表结点类型
ElemType data;//每个结点存放一个数据元素
struct Lnode* next;//指针指向下一个结点
}LNode,*LinkList;//LNode和* LinkList是两个由typedef定义的类型别名。
LNode为结点,LinkList为一个单链表
要表示一个单链表时,只需要声名一个头指针L,指向单链表的第一个结节
LNode *L;//声明一个指向单链表第一个结点的指针
或
LinkList L;//声明一个指向单链表第一个结点的指针
头指针和头结点
在数据结构中,头结点和头指针是链表结构中常用的概念,它们之间有密切的关系,但表示的意义不同。
头指针是一个普通的指针,它的作用是指向链表中的第一个结点。当链表为空时,头指针通常指向NULL。头指针是我们访问链表中的其他结点的起点。
头结点则是链表中的一个特殊结点,它位于链表的第一个元素之前。头结点不存储任何链表数据元素的信息,其主要作用是为了方便对链表的操作,尤其是当链表需要进行频繁的插入和删除操作时。头结点的存在可以使得插入和删除操作不需要修改头指针,因为头结点始终保持在链表的起始位置,头指针始终指向头结点。
头结点和头指针的关系可以总结如下:
- 头指针是一个指针,指向链表的第一个结点(或者头结点,如果有的话)。
- 头结点是链表中的一个结点,位于链表第一个元素之前,其存在是为了简化链表操作。
- 当链表中有头结点时,头指针通常指向头结点;没有头结点时,头指针直接指向链表的第一个数据元素。
- 头结点确保了链表至少有一个结点,不会出现空链表的情况,这样头指针就不需要为空,简化了代码中对空链表的判断处理。
在实际应用中,是否使用头结点取决于具体的需求和设计选择。使用头结点可以统一空表和非空表的处理逻辑,但同时也占用了额外的存储空间。不使用头结点则可以节省空间,但可能需要在不同情况下编写不同的处理代码。
单链表的初始化
不带头结点的单链表
#include <stdio.h>
typedef struct LNode { //定义单链表结点类型
int data;//每个结点存放一个数据元素
struct Lnode* next;//指针指向下一个结点
}LNode, * LinkList;
//初始化一个空的单链表
bool InitList(LinkList& L) {
L = NULL;//空表,暂时还没有任何结点
return true;
}
void main(){
LinkList L;//声明一个指向单链表的指针
InitList(L);//初始化一个空表
}
判断单链表是否为空(不带头结点)
bool Empty(LinkList L) {
if (L== NULL) {
return true;
}
else {
return false;
}
}
带头结点的单链表
#include <stdio.h>
#include <stdlib.h>
typedef struct LNode { //定义单链表结点类型
int data;//每个结点存放一个数据元素
struct LNode* next;//指针指向下一个结点
}LNode, * LinkList;
//初始化一个空的单链表
bool InitList(LinkList& L) {
L = (LNode*)malloc(sizeof(LNode));//分配一个头结点
if (L == NULL) {
return false;//内存不足,分配失败
}
L->next = NULL;//头结点之后暂时还没有节点
return true;
}
void main(){
LinkList L;//声明一个指向单链表的指针
InitList(L);//初始化一个空表
}
判断单链表是否为空(带头结点)
bool Empty(LinkList L) {
if (L->next == NULL) {
return true;
}
else {
return false;
}
}