单链表的定义
定义
用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。因此,为了表示每个数据元素a,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(直接后继的存储位置)。这两部分信息组成数据元素a的存储映像,称为节点(node)。它包括两个域:其中存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域。指针域中存储的信息称作指针或链。n个节点[a(1<i<n)的存储映像]链接成一个链表,即为线性表:
(aj, a2,…,an)
的链式存储结构。又由于此链表的每个节点中只包含一个指针域,故又称线性链表或单链表。
typedef struct LNode {
int data;
struct LNode* next;
}LNode, * LinkList;
单链表的存储结构
下图所示为线性表的单链表存储结构,整个链表的存取必须从头指针开始进行,头指针指示链表中第一个节点(第一个数据元素的存储映像,也称首元节 第点)的存储位置。同时,由于最后一个数据元素没有直接后继,则单链表中最后一个节点的指针为空(NULL)。
单链表的逻辑状态
通常将链表画成用箭头相链接的节点的序列,节点之间的箭头表示链域中的指针。上图所示的单链表可画成下图所示的形式,这是因为在使用链表时,关心的只是它所表示的线性表中数据元素之间的逻辑顺序,而不是每个数据元素在存储器中的实际位置。
单链表操作
头节点
一般情况下,为了处理方便,在单链表的第一个节点之前附设一个节点,称之为头节点。上图所示的单链表增加头节点后如下图所示。
说明
- 首元节点是指链表中存储第一个数据元素a,的节点。如图2.8或图2.9所示的节点“ZHAO”
- 头节点是在首元节点之前附设的一个节点,其指针域指向首元节点。头节点的数据域可以不存储任何信息,也可存储与数据元素类型相同的其他附加信息。例如,当数据元素为整型时,头节点的数据域中可存放该线性表的长度。
- 头指针是指向链表中第一个节点的指针。若链表设有头节点,则头指针所指节点为线性表的头节点;若链表不设头节点,则头指针所指节点为该线性表的首元节点。
初始化
单链表的初始化操作就是构造一个只有头节点的空表。
void InitList(LinkList& L)
{
L = (LNode*)malloc(sizeof(LinkList));
L->next = NULL;
}
取值
和顺序表不同,链表中逻辑相邻的节点并没有存储在物理相邻的单元中,这样,根据给定的节点位置序号i,在链表中获取该节点的值不能像顺序表那样随机访问,而只能从链表的首元节点出发,顺着链域next逐个节点向下访问。
void GetElem(LinkList* L, int i, ElemType& e)
{//在带头节点的单链表L中根据序号1获取元素的值,用e返回L中第1个数据元素的值
LinkList* p;
p = L->next; int j = 1; //初始化P指向首元节点,计数器初值赋为1
while (p && j < i) //顺链域向后查找,直到P为空或p指向第1个元素
{
p = p->next; //p指向下一个节点
j++; //计数器j相应加1
}
if (!p || j > i)
{
return 0; //i值不合法i>n或i<=0
}
e - p->data; //取第i个节点的数据域
return 1;
}
查找
链表中按值查找的过程和顺序表类似,从链表的首元节点出发,依次将节点值和给定值e进行比较,返回查找结果。
按值查找
//按值查找:查找x在L中的位置
LNode* LocateElem(LinkList L, int x)
{
LNode* p = L->next;
while (p && p->data != x) {
p = p->next;
}
return p;
}
按位查找
//按位查找:查找在单链表L中第i个位置的结点
LNode* GetElem(LinkList L, int i) {
int j = 1;
LNode* p = L->next;
if (i == 0)return L;
if (i < 1)return NULL;
while (p && j < i) {
p = p->next;
j++;
}
return p; //如果i大于表长,p=NULL,直接返回p即可
}
插入
假设要在单链表的两个数据元素a和b之间插入一个数据元素x,已知p为其单链表存储结构中指向节点a的指针,如图(a)所示。
为插入数据元素x,首先要生成一个数据域为x的节点,然后将之插入单链表中。根据插入操作的逻辑定义,还需要修改节点a中的指针域,令其指向节点x,而节点x中的指针域应指向节点b,从而实现3个元素a、b和x之间逻辑关系的变化。插入后的单链表如图(b)所示。
void Insert(LinkList& L, int i, int x)
{
LNode* p = GetElem(L, i - 1);
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = x;
s->next = p->next;
p->next = s;
}
删除
要删除单链表中指定位置的元素,同插入元素一样,首先应该找到该位置的前驱节点。如下图所示,在单链表中删除元素b时,应该首先找到其前驱节点a。为了在单链表中实现元素a、b和c之间逻辑关系的变化,仅需修改节点a中的指针域即可。
但在删除节点b时,除了修改节点a的指针域外,还要释放节点b所占的空间,所以在修改指针前,应该引入另一指针q,临时保存节点b的地址以备释放。
void Delete(LinkList& L, int i)
{
if (i<1 || i>Length(L)) {
cout << "delete failed: index is wrong." << endl;
return;
}
LNode* p = GetElem(L, i - 1);
LNode* q = p->next;
p->next = q->next;
free(q);
}
创建单链表
前插法
前插法是通过将新节点逐个插入链表的头部(头节点之后)来创建链表,每次申请一个新点,读入相应的数据元素值,然后将新节点插入到头节点之后。
下图所示为线性表(b,c,d,e)前插法的创建过程,因为每次插入在链表的头部,所以应逆位序输入数据,依次输入e、d、c、b,输入顺序和线性表中的逻辑顺序是相反的。
LinkList HeadInsert(LinkList& L)
{
InitList(L); //初始化
int x;
cin >> x;
while (x != 9999) {
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = x;
s->next = L->next;
L->next = s;
cin >> x;
}
return L;
}
后插法
后插法是通过将新节点逐个插入链表的尾部来创建链表。同前插法一样,每次申请一个新节点,读入相应的数据元素值。不同的是,为了使新节点能够插入表尾,需要增加一个尾指针r指向链表的尾节点。
LinkList TailInsert(LinkList& L)
{
InitList(L);
LNode* s, * r = L;
int x;
cin >> x;
while (x != 9999) {
s = (LNode*)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;
cin >> x;
}
r->next = NULL;
return L;
}
完整代码
#include <stdlib.h>
#include <iostream>
using namespace std;
typedef struct LNode
{
int data;
struct LNode* next;
}LNode, * LinkList;
//初始化
void InitList(LinkList& L)
{
L = (LNode*)malloc(sizeof(LinkList));
L->next = NULL;
}
//遍历操作
void PrintList(LinkList L)
{
LNode* p = L->next;
while (p) {
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
//求单链表的长度
int Length(LinkList L) {
LNode* p = L->next;
int len = 0;
while (p) {
len++;
p = p->next;
}
return len;
}
//按值查找:查找x在L中的位置
LNode* LocateElem(LinkList L, int x)
{
LNode* p = L->next;
while (p && p->data != x) {
p = p->next;
}
return p;
}
//按位查找:查找在单链表L中第i个位置的结点
LNode* GetElem(LinkList L, int i) {
int j = 1;
LNode* p = L->next;
if (i == 0)return L;
if (i < 1)return NULL;
while (p && j < i) {
p = p->next;
j++;
}
return p; //如果i大于表长,p=NULL,直接返回p即可
}
//将x插入到单链表L的第i个位置上
void Insert(LinkList& L, int i, int x)
{
LNode* p = GetElem(L, i - 1);
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = x;
s->next = p->next;
p->next = s;
}
//删除操作:将单链表中的第i个结点删除
void Delete(LinkList& L, int i)
{
if (i<1 || i>Length(L)) {
cout << "delete failed: index is wrong." << endl;
return;
}
LNode* p = GetElem(L, i - 1);
LNode* q = p->next;
p->next = q->next;
free(q);
}
//头插法建立单链表
LinkList HeadInsert(LinkList& L)
{
InitList(L); //初始化
int x;
cin >> x;
while (x != 9999) {
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = x;
s->next = L->next;
L->next = s;
cin >> x;
}
return L;
}
//尾插法建立单链表
LinkList TailInsert(LinkList& L)
{
InitList(L);
LNode* s, * r = L;
int x;
cin >> x;
while (x != 9999) {
s = (LNode*)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;
cin >> x;
}
r->next = NULL;
return L;
}
//按值查找:查找x在L中的位置
LNode* LocateElem(LinkList L, int x)
{
LNode* p = L->next;
while (p && p->data != x) {
p = p->next;
}
return p;
}
//按位查找:查找在单链表L中第i个位置的结点
LNode* GetElem(LinkList L, int i) {
int j = 1;
LNode* p = L->next;
if (i == 0)return L;
if (i < 1)return NULL;
while (p && j < i) {
p = p->next;
j++;
}
return p; //如果i大于表长,p=NULL,直接返回p即可
}
//将x插入到单链表L的第i个位置上
void Insert(LinkList& L, int i, int x)
{
LNode* p = GetElem(L, i - 1);
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = x;
s->next = p->next;
p->next = s;
}
//删除操作:将单链表中的第i个结点删除
void Delete(LinkList& L, int i)
{
if (i<1 || i>Length(L)) {
cout << "delete failed: index is wrong." << endl;
return;
}
LNode* p = GetElem(L, i - 1);
LNode* q = p->next;
p->next = q->next;
free(q);
}