varch/doc/dList.md
2024-07-30 00:57:01 +08:00

559 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 介绍
链表是一种数据结构,其中的数据元素逻辑上连续,但在物理上可以分散存储。链表能够通过指针将多个相同类型的数据块链接成一个完整的序列,在数据结构的实现中具有重要作用。
dList模块为通用的双向链表模块其与sList模块非常相似区别在于指针域由单向链表的一个指向改为两个指向一个指向下一个一个指向上一个如此在存储结构上也有差异sList为单向开环结构dList为双向闭环结构首尾相连形成环形
在API方面使用是和sList基本一致的底层实现不太一样其余性能和优缺点对比在下文展示。
## 接口
### 创建dList
```c
dList *dList_create(void);
```
在dList中dList既是一个链表也是一个结点因为结点也就是长度为1的链表。所以此方法是创建一个为空的并且长度为1的链表也就是创建一个空结点
### 删除dList
```c
void dList_delete(dList *list);
```
此方法会删除该链表(包含其所有的结点)。
### 设置和获取dList结点内容
```c
int dList_set(dList *list, void* data, int size);
int dList_get(dList *list, void* data, int size);
```
当一个结点被创建好之后,数据域是不具备内容的,需要通过`dList_set`方法设置其数据域内容,并且可以使用`dList_get`方法来获取结点数据域内容。
`dList_set`方法会将原来的数据覆盖掉,同时也是指定`size`为 **0** 来删除dList结点数据内容。
```c
static void test_set(void)
{
dList *list = NULL;
int dataInt = 3;
char *dataString = "Hello dList";
list = dList_create();
if (!list)
{
printf("dList_create Fail!\r\n");
return;
}
printf("dList_create Success!\r\n");
dList_set(list, &dataInt, sizeof(dataInt));
printf("list->data %d\r\n", dList_ref(list, int));
dList_set(list, dataString, strlen(dataString) + 1);
printf("list->data %s\r\n", ((char *)(list->data)));
dList_delete(list);
}
```
结果:
```
dList_create Success!
list->data 3
list->data Hello dList
```
示例中的`dList_ref`为数据引用,具体用法在下文
### dList插入数据
```c
dList *dList_insert(dList **listRef, int index, void *data, int size);
```
插入数据方法使用起来会更加简便,省去创建结点和设置数据的环节(即使是链表表头也可以省略创建,而由此方法内部完成),可以灵活的将指定数据插入到指定的位置上。
```c
static void test_insert(void)
{
dList *list = NULL;
for (int i = 0; i < 2; i++)
{
if (!dList_insert(&list, -1, &i, sizeof(i))) goto FAIL;
}
int i = 100;
if (!dList_insert(&list, -1, &i, sizeof(i))) goto FAIL;
dList_forEachForward(list, n)
{
printf("data %d\r\n", dList_ref(n, int));
}
printf("------------\r\n");
dList_forEachReverse(list, n)
{
printf("data %d\r\n", dList_ref(n, int));
}
FAIL:
dList_delete(list);
}
```
结果:
```
data 0
data 1
data 100
------------
data 100
data 1
data 0
```
示例中的`dList_forEachForward`和`dList_forEachReverse`为遍历方法,具体用法在下文。
对于传入dList引用为空时会在首次创建结点并生成表头传入`index`为负数表示插入到尾部。可以使用默认定义好的`dList_front`和`dList_back`宏定义来代表头部和尾部。
### dList擦除数据
```c
int dList_erase(dList **listRef, int index, dList **outPrev);
```
此方法对照`dList_insert`方法,擦除指定位置数据(会将该结点从链表中删除),同时为了更灵活使用,也支持获取被擦除的上一个结点(可以更便利高效得完成连续擦除)。
```c
static void test_erase(void)
{
dList *list = NULL;
for (int i = 0; i < 5; i++)
{
if (!dList_insert(&list, -1, &i, sizeof(i))) goto FAIL;
}
dList_erase(&list, 0, NULL);
dList_forEachForward(list, n)
{
printf("data %d\r\n", dList_ref(n, int));
}
FAIL:
dList_delete(list);
}
```
结果:
```
data 1
data 2
data 3
data 4
```
对照前面插入的例子,擦除表头。
### dList推入和弹出
```c
int dList_pushFront(dList **listRef, void *data, int size);
int dList_pushBack(dList **listRef, void *data, int size);
int dList_popFront(dList **listRef);
int dList_popBack(dList **listRef);
```
分别就是头插、尾插、头删、尾删方法,其实就是在`dList_insert`和`dList_erase`方法基础上针对常用场景进行封装,使用更简便。
```c
static void test_pop(void)
{
dList *list = NULL;
for (int i = 0; i < 5; i++)
{
if (!dList_pushFront(&list, &i, sizeof(i))) goto FAIL;
}
for (int i = 0; i < 5; i++)
{
if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL;
}
dList_popBack(&list);
dList_popBack(&list);
dList_popFront(&list);
dList_forEachForward(list, n)
{
printf("data %d\r\n", dList_ref(n, int));
}
FAIL:
dList_delete(list);
}
```
结果:
```
data 3
data 2
data 1
data 0
data 0
data 1
data 2
```
### dList追加
```c
int dList_append(dList *list, dList **append);
```
此方法可以将两个链表拼接成一个链表,`append`链表在拼接成功后会失效。
**注意** `append`需为表头,虽然即使不是表头也能拼接成功,但是其还属于原来的链表中,在操作时会出现一些意外。
```c
static void test_append(void)
{
dList *list = NULL, *ap = NULL;
for (int i = 0; i < 10; i++)
{
if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL;
if (!dList_pushBack(&ap, &i, sizeof(i))) goto FAIL;
}
if (!dList_append(list, &ap)) goto FAIL;
printPoint(ap);
dList_forEachForward(list, n)
{
printf("data %d\r\n", dList_ref(n, int));
}
FAIL:
dList_delete(list);
dList_delete(ap);
}
```
结果:
```
ap: 00000000
data 0
data 1
data 2
data 3
data 4
data 5
data 6
data 7
data 8
data 9
data 0
data 1
data 2
data 3
data 4
data 5
data 6
data 7
data 8
data 9
```
### dList链接结点
```c
dList *dList_attach(dList **listRef, int index, dList *attach);
```
这个方法是将一个结点(或者一个链表)链接到现有的一个链表当中可以通过index来指定具体链接到哪个位置这个方法很灵活直接操作链表结构**一般情况使用不上此方法**,而是使用此方法所封装的`dList_insert`等方法。此方法可以搭配其他方法灵活二次封装成其他方法。
```c
static void test_attach(void)
{
dList *list = NULL, *a = NULL;
for (int i = 0; i < 5; i++)
{
if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL;
}
for (int i = 0; i < 3; i++)
{
if (!dList_pushBack(&a, &i, sizeof(i))) goto FAIL;
}
dList_attach(&list, -1, a);
dList_forEachForward(list, n)
{
printf("data %d\r\n", dList_ref(n, int));
}
FAIL:
dList_delete(list);
}
```
结果:
```
data 0
data 1
data 2
data 3
data 4
data 0
data 1
data 2
```
### dList断链结点
```c
dList *dList_detach(dList **listRef, int begin, int end, dList **outPrev);
```
这个方法与`dList_attach`方法为对照方法可以从链表中断链出来若干个结点子链表可以通过index来指定具体断链哪个位置和count指定个数这个方法很灵活直接操作链表结构**一般情况使用不上此方法**,而是使用此方法所封装的`dList_erase`等方法。此方法可以搭配其他方法灵活二次封装成其他方法。
```c
static void test_detach(void)
{
dList *list = NULL, *node = NULL;
for (int i = 0; i < 10; i++)
{
if (!dList_insert(&list, -1, &i, sizeof(i))) goto FAIL;
}
#if 1
node = dList_detach(&list, 0, 3, NULL);
if (!node)
{
printf("dList_detach fail\r\n");
}
#endif
dList_forEachForward(node, n)
{
printf("node data %d\r\n", dList_ref(n, int));
}
dList_delete(node);
dList_forEachForward(list, n)
{
printf("data %d\r\n", dList_ref(n, int));
}
FAIL:
dList_delete(list);
}
```
结果:
```
node data 0
node data 1
node data 2
node data 3
data 4
data 5
data 6
data 7
data 8
data 9
```
### dList复制
```c
dList *dList_copy(dList *list, int begin, int end);
```
这个方法是会根据源链表的指定区间进行深拷贝一份新的链表。
```c
static void test_copy(void)
{
dList *list = NULL, *copy = NULL;
for (int i = 0; i < 10; i++)
{
if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL;
}
copy = dList_copy(list, -5, -1);
if (!copy)
{
printf("dList_copy fail\r\n");
}
dList_forEachForward(copy, n)
{
printf("data %d\r\n", dList_ref(n, int));
}
FAIL:
dList_delete(list);
dList_delete(copy);
}
```
结果:
```
data 5
data 6
data 7
data 8
data 9
```
### dList区间翻转
```c
int dList_reverse(dList *list, int begin, int end);
```
这个方法是会根据源链表的指定区间进行翻转。
```c
static void test_reverse(void)
{
dList *list = NULL;
for (int i = 0; i < 10; i++)
{
if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL;
}
if (!dList_reverse(list, 1, 5))
{
printf("dList_reverse fail\r\n");
}
dList_forEachForward(list, n)
{
printf("data %d\r\n", dList_ref(n, int));
}
FAIL:
dList_delete(list);
}
```
结果:
```
data 0
data 5
data 2
data 3
data 4
data 1
data 6
data 7
data 8
data 9
```
### dList获取指定结点
```c
dList *dList_to(dList *list, int index);
```
这个方法可以根据表头当前位置获取偏移指定位置的结点,传入负数可以从末端往回找。
```c
static void test_to(void)
{
dList *list = NULL, *node;
for (int i = 0; i < 10; i++)
{
if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL;
}
node = dList_to(list, -6);
if (!node)
{
printf("dList_to fail\r\n");
goto FAIL;
}
printf("dList_to data %d\r\n", dList_ref(node, int));
dList_forEachForward(list, n)
{
printf("data %d\r\n", dList_ref(n, int));
}
FAIL:
dList_delete(list);
}
```
结果:
```
dList_to data 4
data 0
data 1
data 2
data 3
data 4
data 5
data 6
data 7
data 8
data 9
```
### dList大小
```c
int dList_size(dList *list);
```
这个方法获取链表的数据个数。
```c
static void test_size(void)
{
dList *list = NULL;
for (int i = 0; i < 10; i++)
{
if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL;
}
printf("size %d\r\n", dList_size(list));
printf("size %d\r\n", dList_size(dList_to(list, 3)));
FAIL:
dList_delete(list);
}
```
结果:
```
size 10
size 7
```
### dList遍历
```c
#define dList_forEach(list, node) // 从前往后遍历
#define dList_forEachForward(list, node) // 从前往后遍历
#define dList_forEachReverse(list, node) // 从后往前遍历
```
这个方法为遍历链表的方法,具体例子可以参考上文其他使用例子。
### dList结点数据引用
```c
#define dList_ref(node, type)
```
这个方法类似C++的引用,实则是操作指针(只是将其隐藏起来),读写更方便,但要注意的是数据操作别越界,比如本来该结点存储的是`char`型数据分配空间也就只分配1个字节大小如果当作`int`型使用那就越界了。具体例子可以参考上文其他使用例子。