链表由多个数据节点组成,每个数据节点由数据域和指针域两部分组成,如下图
其中数据域的类型取决于节点数据类型,若节点为字符,则数据域类型为字符型。指针域类型为地址,存储着其他节点的内存地址,在C/C++中表现为节点类型的指针。
计算机存储链表的过程中以数据节点为基本单位,每个节点都是不可再分的,每次只申请一个节点大小的内存空间,不同节点之间通过指针域连接。这意味着如果在原链表上增加和删除节点十分方便,不必移动其他数据,只需要更改相应的指针域中的内存地址就好,但是当我们访问链表时将会变得十分不方便,只能通过一个又一个节点的指针域不断跳转。
常见链表分为单链表和双链表,区别在于指针域中指针的个数。单链表节点的指针域中只有一个指向下一个节点的指针,指针域为空时代表该节点为最后一个节点。这就意味着如果是一个单链表,当我们访问的时候,就只能“一条路走到黑”,没有其他标记的时候就没有办法返回到上一个节点。为了弥补单链表的这种缺陷,我们可以设计双链表,双链表的指针域中不仅有指向下一个节点的指针,还有一个指向上一个节点的指针。
链表的增加与删除,以单链表为例。假设某单链表有三个节点a、 b、 c如下,它们的内存地址分别是0x10、0x20、0x30
如果我们要删除节点b,只需要修改a节点的指针域地址为c节点的所在地址(0x30),释放b节点的内存资源即可。
增加节点则恰恰相反,加入我们需要在a、c两节点之间在添加一个节点时,只需要申请一个节点b的内存,设置b节点的指针域为c的节点的地址,然后把a节点的指针域修改为b节点的地址。一般情况下我们的需求可能是把b节点添加到a节点后面,是没有c节点的,一定要先将b的指针域设置为a的指针域的值,然后在设置a的指针域,否则我们将无法找到原本a节点后面的节点。
节点的查询,修改过程不再赘述。
下面是单链表的C++简单实现。
定义节点数据
#define dataType int //节点数据类型
typedef struct Node {
dataType data; //数据域
struct Node *next; //指针域
} *list;
插值函数,后插法可以改进非递归实现,非递归实现的时候需要注意因为参数L是引用,不要更头部节点L以及判断L是否为空
void insert(list &L, dataType value) { //链表插值函数,L为链表,value为插入的值
//以下为前插法,即每次添加的节点都位于链表的第一个
list p = L;
L = (list) malloc(sizeof(list));
L->data = value;
L->next = p;
//以下为后插法,每次插入的节点位于链表结尾
// if(L == NULL) {
// L = (list)malloc(sizeof(list));
// L->data = value;
// L->next = NULL;
// return;
// }
// else {
// insert(L->next, value);
// }
}
从链表中删除指定值,因为传参的过程中L是引用,所以删除节点后链表的结构不会被破坏。
void deleteByValue(list &L, dataType value) { //删除指定值的节点,L为链表,value为匹配值
if(L == NULL) {
return;
}
if(L->data == value) { //当前节点需要删除
list p = L;
L = L->next;
free(p);
deleteByValue(L, value);
}
else { //当前节点无需删除
deleteByValue(L->next, value);
}
}
链表的打印以及销毁
void printList(list L) { //打印链表
if(L == NULL) {
return;
}
cout << L->data;
for (L = L->next; L != NULL; L = L->next) {
cout << "--->" << L->data;
}
cout << endl;
}
void destoryList(list &L) { //销毁链表
list p;
while(L != NULL) {
p = L;
L = L->next;
free(p);
}
}
文件下载