目录
一 双向链表
1.1 双向链表概述
双向链表就是在单链表的基础上,在定义结构体的时候做了一些手脚,在单链表中结构体的内容为数据以及指向下一个结点的指针,在双向链表中,增加了指向前一个结点元素的指针,即实现了对指定元素前驱和后继的查询。代价就是增加了1/3的空间储存。
内存分配图如下:
1.2 双向链表的优势
双向链表实现了一个十分重要的功能是自我删除。即:
p->previous->next=p->next;
另外定义的Note来储存前后结点的信息。
1.3 结构体及宏定义
typedef char ElemType;
#define OK 1
#define ERROR 0
typedef struct DoubleLinkedNote
{
ElemType data;
struct DoubleLinkedNote *Previous;
struct DoubleLinkedNote *Next;
} DLNote, *DLNotePtr, *DoubleLink;
在老师命名基础上修改了一点,比如DLNotePtr代表结点,DoubleLink代表链表,使程序具有更好的可读性
1.4 初始化链表
/*初始化*/
DoubleLink Init()
{
DLNotePtr tempHeader;
tempHeader = (DLNotePtr)malloc(sizeof(DLNote));
tempHeader->data = 0;
tempHeader->Previous = NULL;
tempHeader->Next = NULL;
return tempHeader;
}
1.5 头插
/*头插*/
int InsertElement(DoubleLink tempHeader, ElemType data)
{
DLNotePtr p, q, r;
p = tempHeader;
if (p->Next == NULL)
{
/* 当双向链表只有一个头结点时 */
r = (DLNotePtr)malloc(sizeof(DLNote));
r->data = data; // 数据域赋值
r->Next = p->Next; // 后向指针连接
p->Next = r;
r->Previous = p; // 将插入结点的Previous 指向头结点
}
else
{
/* 当双向链表不只一个头结点时 */
r = (DLNotePtr)malloc(sizeof(DLNote));
r->data = data; // 数据域赋值
p->Next->Previous = r; // 头结点的下一个结点的Previous 指针
r->Previous = p; // 插入的结点的Previous 指针指向头结点
r->Next = p->Next; // 插入结点的Next 指针指向原本头指针的下一结点
p->Next = r; // 将头结点的Next 指针指向插入结点
}
}
之前一直尝试的是尾插法,这次采用了头插的方式。值得注意的是,一定要分类讨论当前链表是否只存在一个结点。
1.6 删除结点
/*删除指定元素*/
int DeleteElem(DLNotePtr tempHeader, ElemType data)
{
DLNotePtr p, q, r;
p = tempHeader;
if (p == NULL)
{
printf("The list is empty\n");
return ERROR;
}
while (p->Next != NULL)
{
p = p->Next;
if (p->data == data)
{
p->Previous->Next = p->Next; // 将被删结点的上一个结点的Next 指针指向 被删结点的下一个结点
p->Next->Previous = p->Previous; // 将被删结点的下一个结点的 Previous 指针指向 被删结点的上一个结点
break;
}
}
free(p); // 释放内存
return OK;
}
1.7 遍历链表
/*遍历打印*/
int Print(DoubleLink tempHeader)
{
DLNotePtr p, q;
p = tempHeader->Next;
while (p)
{
printf("%c ", p->data);
p = p->Next;
}
printf("\n");
return OK;
}
1.8 测试代码
int main()
{
DoubleLink my_List;
my_List = Init();
/*插入*/
InsertElement(my_List, 'a');
InsertElement(my_List, 'b');
InsertElement(my_List, 'c');
InsertElement(my_List, 'd');
Print(my_List);
/*删除指定元素*/
DeleteElem(my_List, 'a');
Print(my_List);
DeleteElem(my_List, 'b');
Print(my_List);
DeleteElem(my_List, 'e');
Print(my_List);
/*销毁链表*/
DestroyList(my_List);
Print(my_List);
return 0;
}
1.9 运行结果
1.10 临摹老师的代码
#include <stdio.h>
#include <malloc.h>
/**
* Double linked list of integers. The key is char.
*/
typedef struct DoubleLinkedNode{
char data;
struct DoubleLinkedNode *previous;
struct DoubleLinkedNode *next;
} DLNode, *DLNodePtr;
/**
* Initialize the list with a header.
* @return The pointer to the header.
*/
DLNodePtr initLinkList(){
DLNodePtr tempHeader = (DLNodePtr)malloc(sizeof(struct DoubleLinkedNode));
tempHeader->data = '\0';
tempHeader->previous = NULL;
tempHeader->next = NULL;
return tempHeader;
}// Of initLinkList
/**
* Print the list.
* @param paraHeader The header of the list.
*/
void printList(DLNodePtr paraHeader){
DLNodePtr p = paraHeader->next;
while (p != NULL) {
printf("%c", p->data);
p = p->next;
}// Of while
printf("\r\n");
}// Of printList
/**
* Insert an element to the given position.
* @param paraHeader The header of the list.
* @param paraChar The given char.
* @param paraPosition The given position.
*/
void insertElement(DLNodePtr paraHeader, char paraChar, int paraPosition){
DLNodePtr p, q, r;
// Step 1. Search to the position.
p = paraHeader;
for (int i = 0; i < paraPosition; i ++) {
p = p->next;
if (p == NULL) {
printf("The position %d is beyond the scope of the list.", paraPosition);
return;
}// Of if
} // Of for i
// Step 2. Construct a new node.
q = (DLNodePtr)malloc(sizeof(struct DoubleLinkedNode));
q->data = paraChar;
// Step 3. Now link.
r = p->next;
q->next = p->next;
q->previous = p;
p->next = q;
if (r != NULL) {
r->previous = q;
}// Of if
}// Of insertElement
/**
* Delete an element from the list.
* @param paraHeader The header of the list.
* @param paraChar The given char.
*/
void deleteElement(DLNodePtr paraHeader, char paraChar){
DLNodePtr p, q, r;
p = paraHeader;
// Step 1. Locate.
while ((p->next != NULL) && (p->next->data != paraChar)){
p = p->next;
}// Of while
// Step 2. Error check.
if (p->next == NULL) {
printf("The char '%c' does not exist.\r\n", paraChar);
return;
}// Of if
// Step 3. Change links.
q = p->next;
r = q->next;
p->next = r;
if (r != NULL) {
r->previous = p;
}// Of if
// Step 4. Free the space.
free(q);
}// Of deleteElement
/**
* Unit test.
*/
void insertDeleteTest(){
// Step 1. Initialize an empty list.
DLNodePtr tempList = initLinkList();
printList(tempList);
// Step 2. Add some characters.
insertElement(tempList, 'H', 0);
insertElement(tempList, 'e', 1);
insertElement(tempList, 'l', 2);
insertElement(tempList, 'l', 3);
insertElement(tempList, 'o', 4);
insertElement(tempList, '!', 5);
printList(tempList);
// Step 3. Delete some characters (the first occurrence).
deleteElement(tempList, 'e');
deleteElement(tempList, 'a');
deleteElement(tempList, 'o');
printList(tempList);
// Step 4. Insert to a given position.
insertElement(tempList, 'o', 1);
printList(tempList);
}// Of appendInsertDeleteTest
/**
* Address test: beyond the book.
*/
void basicAddressTest(){
DLNode tempNode1, tempNode2;
tempNode1.data = 4;
tempNode1.next = NULL;
tempNode2.data = 6;
tempNode2.next = NULL;
printf("The first node: %d, %d, %d\r\n",
&tempNode1, &tempNode1.data, &tempNode1.next);
printf("The second node: %d, %d, %d\r\n",
&tempNode2, &tempNode2.data, &tempNode2.next);
tempNode1.next = &tempNode2;
}// Of basicAddressTest
/**
* The entrance.
*/
void main(){
insertDeleteTest();
basicAddressTest();
}// Of main
1.11 思考&总结
虽然双向链表只是比单向链表在结构体增加了一个前驱的指针,但是在插入操作的时候,指针的赋值顺序变得更加重要。一不小心就会出先乌龙。
此外,每写完一个函数就要去测试啊!千万不要最后测试,要不会输的很惨,调试太难了。
此外,由于头结点的前驱指向了NULL,在链表只存在头节点进行插入和删除操作的时候,要特别地处理这个NULL,因为和其他常规结点不一样,指向NULL,把NULL赋给一个指针变量,可能会导致不可预估地错误。
二 静态链表
2.1 概述
逻辑结构上相邻的数据元素,存储在指定的一块内存空间中,数据元素只允许在这块内存空间中随机存放,这样的存储结构生成的链表称为静态链表。静态链表的特点在于把顺序表和单链表地优点结合了起来。但同时也继承了其中的一些缺点。
2.2 优点&缺点
优点:删除和插入元素时间复杂度低;
缺点:需要提前分配一块较大的空间。无法进行动态地操作
2.3 示意图
2.4 原理
静态链表实际上存在两个单链表。一个是用于管理已用结点,头节点通常为下标为0的元素。另外一个是管理未用结点的,头节点通常为下标为1的元素。
仔细观察上图,我们可以发现,区别于朴素的数组,静态链表中的结点使用自己的下标来代表自己的位置,同时定义了一个int型变量来储存游标元素,所谓游标也就是指的是指向下一个位置的下标。这样就在一个固定的空间内实现了类似单链表的操作。
OK,我们来看代码:
2.5 结构体&宏定义
#define MAX 100
#define ElemType char
typedef struct ListNode
{
ElemType data;
int cur; //静态链表中的游标
}ListNode;
//静态链表实际上就是一个结构体数组
typedef ListNode StaticList[MAX];
2.6 静态链表的初始化
//静态链表的初始化
void InitSList(StaticList &p)
{
for(int i=1; i<MAX; ++i)
{
p[i].cur = i+1;
}
p[MAX].cur = 0;
p[0].cur = -1;
}
需要注意对下标为0和下标为1的元素做处理。
2.7 遍历
//遍历
void PrintList(StaticList &p)
{
int i = p[0].cur;//从第一个数据结点开始搜索
while(i != -1)//将所有的数据结点打印
{
printf("%c-->",p[i].data);
i = p[i].cur;
}
printf("Nul.\n");
}
2.8 头插
//静态链表的头插
void Insert(StaticList &p, ElemType x)
{
int i = Malloc_SL(p);
if (i == 0)
{
printf("申请节点空间失败.\n");
return;
}
p[i].data = x;
if (p[0].cur == -1)
{
p[i].cur = -1;
// p[0].cur = i;
}
else
{
p[i].cur = p[0].cur;
}
p[0].cur = i;
}
需要去考虑申请空间不足的情况,比较这是一个固定大小的空间。
2.9 回收空间
//回收空间
void Free_SL(StaticList &p, int k)
{
p[k].cur = p[1].cur;
p[1].cur = k;
}
也就需要处理自己的cur值。
2.10 头删
void Delete(StaticList &p)
{
int i = p[0].cur;
p[0].cur = p[i].cur;
Free_SL(p,i);
}
头删的过程中也处理了free。
2.11 测试代码与结果
int main()
{
StaticList SL;
InitSList(SL);
for(int i=0; i<8; ++i)
{
Insert(SL,'A'+i);
}
ShowSList(SL);
Delete(SL);
ShowSList(SL);
Insert(SL,'Z');
ShowSList(SL);
return 0;
}
2.12 总结&反思
静态链表是一个小型的操作系统管理内存的缩影。其中核心的问题就是注意是否超内存,以及对已经使用内存的回收。
另外我们单独实现了内存单元的FREE,同时也要注意在头删函数中本身就已经存在了FREE操作。
对于查询和删除以及插入操作,数据量大的时候十分友好。
以上。