纸上谈兵: 表 (list)
表
表(list)是常见的数据结构。从数学上来说,表是一个有序的元素集合。在C语言的内存中,表储存为分散的节点(node)。每个节点包含有一个元素,以及一个指向下一个(或者上一个)元素的指针。如下图所示:
表: 橙色储存数据,蓝色储存指针
图中的表中有四个节点。第一个节点是头节点(head node),这个节点不用于储存元素,只用于标明表的起始。头节点可以让我们方便的插入或者删除表的第一个元素。整个表中包含有三个元素(5, 2, 15)。每个节点都有一个指针,指向下一个节点。最后一个节点的指针为NULL,我们用“接地”来图示该指针。
表的功能与数组(array)很类似,数组也是有序的元素集合,但数组在内存中为一段连续内存,而表的每个节点占据的内存可以是离散的。在数组中,我们通过跳过固定的内存长度来寻找某个编号的元素。但在表中,我们必须沿着指针联系起的长链,遍历查询元素。此外,数组有固定的大小,表可以根据运行情况插入或者删除节点,动态的更改大小。表插入节点时需要从进程空间的堆中开辟内存空间,用以储存节点。删除节点可以将节点占据的内存归还给进程空间。
删除节点, free释放内存
插入节点,malloc开辟内存
表有多种变种。上面的表中,指针指向是从前向后的,称为单向链表(linked list)。还有双向链表(double-linked list),即每个节点增加一个指向前面一个元素的指针。以及循环链表(tabular list),最后一个元素的指针并不为NULL,而是指向头节点。不同类型的链表有不同的应用场景。
双向链表
循环链表
双向循环链表
单向链表的C实现
一个数据结构的实现有两方面: 1. 数据结构的内存表达方式; 2. 定义在该数据结构上的操作。我们这里实现最简单的单向链表。表所支持的操作很灵活多样,我们这里定义一些最常见的操作。每个操作都写成一个函数。
/* By Vamei */
#include <stdio.h>
#include <stdlib.h>
typedef struct node *LIST;
typedef struct node *position;
/* node,节点 */
struct node {
int element;
position next;
};
/*
* operations (stereotype)
* 操作
*/
LIST init_list(void);
void delete_list(LIST);
int is_null(LIST);
void insert_node(position, int);
void delete_node(LIST, position);
position find_last(LIST);
position find_value(LIST, int);
position find_previous(LIST, position);
void print_list(LIST);
/* for testing purpose */
void main()
{
LIST L;
position np;
int i;
/* elements to be put into the list */
int a[] = {1, 3, 5, 7, 9};
/* initiate a list */
L = init_list();
print_list(L);
/* insert nodes. Insert just after head node */
for (i=4; i>=0; i--) {
insert_node(L, a[i]);
}
print_list(L);
/* delete first node with value 5 */
np = find_value(L, 5);
delete_node(L, np);
print_list(L);
/* delete list */
delete_list(L);
/* initiate a list */
L = init_list();
print_list(L);
/* insert nodes. Insert just after head node */
for (i=4; i>=0; i--) {
insert_node(L, a[i]);
}
print_list(L);
/* delete list */
delete_list(L);
}
/*
* Traverse the list and print each element
* 打印表
*/
void print_list(LIST L)
{
position np;
if(is_null(L)) {
printf("Empty Listnn");
return;
}
np = L;
while(np->next != NULL) {
np = np->next;
printf("%p: %d n", np, np->element);
}
printf("n");
}
/*
* Initialize a linked list. This list has a head node
* head node doesn't store valid element value
* 创建表
*/
LIST init_list(void)
{
LIST L;
L = (position) malloc(sizeof(struct node));
L->next = NULL;
return L;
}
/*
* Delete all nodes in a list
* 删除表
*/
void delete_list(LIST L)
{
position np, next;
np = L;
do {
next = np->next;
free(np);
np = next;
} while(next != NULL);
}
/*
* if a list only has head node, then the list is null.
* 判断表是否为空
*/
int is_null(LIST L)
{
return ((L->next)==NULL);
}
/*
* insert a node after position np
* 在np节点之后,插入节点
*/
void insert_node(position np, int value)
{
position nodeAddr;
nodeAddr = (position) malloc(sizeof(struct node));
nodeAddr->element = value;
nodeAddr->next = np->next;
np->next = nodeAddr;
}
/*
* delete node at position np
* 删除np节点
*/
void delete_node(LIST L, position np)
{
position previous, next;
next = np->next;
previous = find_previous(L, np);
if(previous != NULL) {
previous->next = next;
free(np);
}
else {
printf("Error: np not in the list");
}
}
/*
* find the last node of the list
* 寻找表的最后一个节点
*/
position find_last(LIST L)
{
position np;
np = L;
while(np->next != NULL) {
np = np->next;
}
return np;
}
/*
* This function serves for 2 purposes:
* 1. find previous node
* 2. return NULL if the position isn't in the list
* 寻找npTarget节点前面的节点
*/
position find_previous(LIST L, position npTarget)
{
position np;
np = L;
while (np->next != NULL) {
if (np->next == npTarget) return np;
np = np->next;
}
return NULL;
}
/*
* find the first node with specific value
* 查询
*/
position find_value(LIST L, int value)
{
position np;
np = L;
while (np->next != NULL) {
np = np->next;
if (np->element == value) return np;
}
return NULL;
}
在main()函数中,我们初始化表,然后插入(1, 3, 5, 7, 9)。又删除元素5。可以看到,节点零散的分布在内存中。删除节点操作不会影响其他节点的存储位置。
我们随后删除表,又重新创建表。可以看到,这次表占据内存的位置与第一次不同。
下面是main()函数的运行结果。
Empty List 0x154d0b0: 1 0x154d090: 3 0x154d070: 5 0x154d050: 7 0x154d030: 9 0x154d0b0: 1 0x154d090: 3 0x154d050: 7 0x154d030: 9 Empty List 0x154d070: 1 0x154d010: 3 0x154d0b0: 5 0x154d090: 7 0x154d050: 9
总结
表: 内存中离散分布的有序节点
插入,删除节点
- 蒙特卡洛算法及其实现
- 【专知-PyTorch手把手深度学习教程05】Dropout快速理解与PyTorch实现: 图文+代码
- 【专知-PyTorch手把手深度学习教程04】GAN快速理解与PyTorch实现: 图文+代码
- 2017年中国大学生程序设计竞赛-中南地区赛暨第八届湘潭市大学生计算机程序设计大赛题解&源码(A.高斯消元,D,模拟,E,前缀和,F,LCS,H,Prim算法,I,胡搞,J,树状数组)
- 【专知中秋呈献-PyTorch手把手深度学习教程03】LSTM快速理解与PyTorch实现: 图文+代码
- BZOJ 3098: Hash Killer II(新生必做的水题)
- [接口测试 - 基础篇] 04 无法绕过的json解析
- 【专知-PyTorch手把手深度学习教程02】CNN快速理解与PyTorch实现: 图文+代码
- [接口测试 - 基础篇] 03 unittest测试框架了解多少才够?
- 关关的刷题日记09——Leetcode 80. Remove Duplicates from Sorted Array II
- [linux][kernel]dump kmsg到文件系统中
- [接口测试 - 基础篇] 12 还是要掌握python日志管理模块的
- Spring整合Quartz分布式调度
- [接口测试 - 基础篇] 11 掌握下python解析YAML格式也是需要的
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- java设计模式-单例模式详解
- 技术迭代迷茫?Android资深架构师教你如何打破这个局面!
- 基于空间矢量的机器人动力学:铰接体惯量法matlab程序
- leetcode树之相同的树
- 1. 两数之和
- 突击并发编程JUC系列-原子更新字段类AtomicStampedReference
- 去除字符数组中指定的字符
- Android——Android Studio开发环境搭建及设置本地Gradle方法
- Java——类集框架:List集合接口的详解及应用举例
- Android——NORDIC nRF5x系列 DFU升级 APP实现
- Java——集合工具类(Collections工具类、Stack子类)
- Android——RecyclerView自定义OnScrollListener实现下拉刷新监听,上拉加载更多功能
- Java——类集框架:Map集合的详解及应用举例
- Java——类集框架:Set集合接口的详解及应用举例
- 如何有效地进行代码 Review?