C++ 数据结构算法 学习笔记(3)-链表
原理精讲
链表是线性表的链式存储方式,逻辑上相邻的数据在计算机内的存储位置不必须相邻,那么 怎么表示逻辑上的相邻关系呢?可以给每个元素附加一个指针域,指向下一个元素的存储位 置。如图所示:
从图中可以看出,每个结点包含两个域:数据域和指针域,指针域存储下一个结点的地址, 因此指针指向的类型也是结点类型
链表的核心要素:
- 每个节点由数据域和指针域组成
- 指针域指向下一个节点的内存地址
其结构体的定义:
typedef struct LinkNode{
ElemType data;
struct LinkNode *next;
}LinkList,LinkNode;
单链表的概念
链表的节点均单向指向下一个节点,形成一条单向访问的数据链
他可以想象成一条链,每一节都挨在一起.如下图所示
需要注意的是链表头结点都不会有数据,它只有指针域,指向下一个元素的存储位.
而最后一个节点会有数据,但是他的指针域会指向NULL
的位置!
单链表的初始化
代码例子如下:
typedef struct _LinkNode
{
int data;
struct _LinkNode* next;
}LinkNode, LinkList;
bool InitList(LinkList*& L)
{
L = new LinkNode;
if (!L) return false;
L->next = NULL;
return true;
}
正如之前所说的, 头结点不会有数据存储,只会有指针域,指向下一个元素的存储位
问题:为什么 LinkList*& L
需要使用引用?
答: 这是因为如果没有引用的话,原本在main
函数里传递的这个指针将传的是一个形参. 这将导致main
函数里的 LinkList*
指针不会正真的指向 InitList
函数里的 new LinkNode
. 当InitList
函数结束时 main
函数里的 LinkList*
还是会指向NULL
单链表的前插法
代码例子如下:
bool ListInsert_front(LinkList*& L, LinkNode* node)
{
if (!L || !node) return false;
node->next = L->next;
L->next = node;
return true;
}
这里需要深入了解的地方是:
- 当一个新的节点插入时,头节点的指针域需指向新的节点位置
- 而新的节点位置需指向之前头节点指向的位置, 所以代码就是以这个原理执行的.
单链表的尾插法
代码例子如下:
bool ListInsert_end(LinkList*& L, LinkNode* node)
{
if (!L || !node) return false;
LinkNode* last = NULL;
last = L;
while (last->next)
{
last = last->next;
}
node->next = NULL;
last->next = node;
}
这里需要注意的地方是:
-
代码里的
while
循环是为了寻找到链表的最后一个节点, 所以里面的判断为last->next
. 当这个为false
时,last
将会指向最后一个节点. -
因此,把node当成最后一个节点修改它的指针域为
NULL
,并且把原本应该是最后一个节点的指针域指向新的node
位置.
单链表任意位置的插入
bool LinkInsert(LinkList*& L, int i, int& e)
{
if (!L) return false;
int j = 0;
LinkList* p, *s;
p = L;
while (p && j < i - 1) //Search for the position i-1 node, p will pointing to the node
{
p = p->next;
j++;
}
if (!p || j > i - 1)
{
return false;
}
s = new LinkNode; //Construct new node
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
这里需要注意的地方是:
-
while (p && j < i - 1)
这个循环之所以要-1是因为我们先要指向要插入节点的位置的前一个节点. 以便可以让当前的这个节点的指针域指向新的节点s
-
代码里需要
if (!p || j > i - 1)
的原因是用户可能输入要插入的值为负数,这样的话是不可能能在链表里插入的,所以我们要做防御式编程. 而!p
的 目的也是一样可能链表只有5个节点 但是用户输入要在第10个节点插入!