链表是线性表的一种存储方式,每个节点存储两个信息:
- 节点数据
- 指向下一个节点的指针
对于链表,只要求逻辑上元素与元素之间相邻,物理上可以将它们离散的存放在存储空间中,通过每个节点的指针来遍历整个链表。由于链表在存储空间中无序,且每个节点只能指向下一个节点,故对链表的访问只能采用顺序访问,不能随机访问。由于链表不要求物理上相邻,对其进行增删等操作不需要像顺序表一样移动大量元素,只需要修改指针。离散存放的链表不需要提前分配连续空间, 长度不受限。
接下来使用代码来理解链表的定义与操作。为方便理解,以下代码中链表皆为单链表的操作。
定义
typedef struct LNode{
int data;
struct LNode *next;
}LNode, *LinkList;
结点中包括数据域和指向下一个节点的指针域。此处的数据域的类型也可以是其他类型。
通常用一个头指针标识一个单链表,头指针指向NULL表示这是一个空表。实际使用时,为了方便操作,会令头指针指向头结点,头结点内部不存储任何信息,也可以存储与链表相关的信息。头结点的指针指向链表的第一个节点。
带头结点和不带头结点的链表的区分:
- 不带头结点的单链表:头指针指向第一个节点。当链表为空时,头指针指向NULL;
- 带头结点的单链表:头指针指向头结点,头结点指向的下一个节点为第一个数据节点。当链表为空时,头结点的指针域为NULL;
初始化
//不带头结点的单链表,初始化
bool Init_List(LinkList &L){
L = NULL;
return true;
}
//带头结点的单链表
bool InitList(LinkList &L){
L = (LNode *)malloc(sizeof(LNode)); //空表,暂时还没有任何节点(防止脏数据)
if (L == NULL)
return false; //若内存不足,分配失败
L->next = NULL; //头结点之后暂时还没有节点,指针域为空
return true;
}
按位插入
对按位插入我们首先来分析有头结点的链表的插入,传入的参数有头指针、插入位置以及数据。首先我们要分析插入位置是否合法。链表中可插入范围为第一个结点前到最后一个节点后。插入位置不合法有两种情况:1. 小于1;2.大于链表长度+1(此时链表中无节点与其逻辑上相邻)。由于链表无法随机访问,要找到插入的位置就需要从头遍历整个链表,遍历时需要设置一个记录值记录当前节点的位置。带头结点的链表头指针指向头结点,因此头结点是第0个节点。将一个节点插入链表需要将其与插入位置的前后结点连接,插入时先将其指针域指向后一个节点,再令前一个节点的指针域指向此节点。由于单链表的每一个节点指向下一个节点,故插入时需要找到前一个节点,再进行插入操作。将以上分析进行分步概述:
- 判断插入位置是否合法;
- 从头遍历整个链表,找到插入位置的前一个节点;
- 进行插入操作;
//向链表指定位置插入元素
bool InsertList(LinkList &L,int i,int e){
if(i < 1) //位序不合法,插入失败
return false;
LNode *p; //指针p指向当前扫描到的头结点
int j = 0; //当前p指向的是第几个节点
p = L; //L指向头结点,头结点是第0个节点
while(p != NULL && j < i-1){
p = p->next;
j++;
}
if(p == NULL) //i值不合法
return fa