单链表是一种数据结构,它由一个节点序列组成,每个节点包含两个部分:数据和指向下一个节点的指针。单链表中只能向一个方向遍历,即从头节点开始,依次向后遍历。该数据结构可以用于实现栈、队列等数据结构,也可用于解决一些具有类似链式结构的问题。由于单链表的节点只包含一个指向下一个节点的指针,因此在插入、删除节点时较为方便,但是在查找节点时需要遍历整个链表。
在链式存储中,结点之间的存储单元地址是不连续的。链式存储中每个结点都包含两个部分:存储元素本身的数据域和存储结点地址的指针域。
单链表的元素插入与删除
#include<stdio.h>
#include<malloc.h>
typedef struct LinkNode
{
char data;
struct LinkNode *next;
} LNode, *LinkList, *NodePtr;
LinkList initLinkList()
{
NodePtr tempHeader = (NodePtr)malloc(sizeof(LNode));
tempHeader->data = '\0';
tempHeader->next = NULL;
return tempHeader;
}// of initLinkList
void printList(NodePtr paraHeader)
{
NodePtr p = paraHeader->next;
while (p != NULL)
{
printf("%c", p->data);
p = p->next;
}// Of while
printf("\r\n");
}// Of printList
void appendElement(NodePtr paraHeader, char paraChar)
{
NodePtr p, q;
// Step 1: Construct a new node
q = (NodePtr)malloc(sizeof(LNode));
q->data = paraChar;
q->next = NULL;
//Step 2: Search to the tail
p = paraHeader;
while (p->next != NULL)
{
p = p->next;
}// Of while
//Step 3: Now add/link
p->next = q;
}// Of appendElement
void insertElement(NodePtr paraHeader, char paraChar, int paraPosition)
{
NodePtr p, q;
//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 = (NodePtr)malloc(sizeof(LNode));
q->data = paraChar;
//Step 3:Now link
printf("linking\r\n");
q->next = p->next;
p->next = q;
}// Of insertElement
void deleteElement(NodePtr paraHeader, char paraChar)
{
NodePtr p, q;
p = paraHeader;
while ((p->next != NULL) && (p->next->data != paraChar))
{
p = p->next;
}// Of while
if (p->next == NULL)
{
printf("Cannot delete %c\r\n", paraChar);
return;
}// Of if
q = p->next;
p->next = p->next->next;
free(q);
}// Of deleteElement
/**
* Unit test.
*/
void appendInsertDeleteTest()
{
//Step 1: Initialize an empty list
LinkList tempList = initLinkList();
printList(tempList);
//Step 2: Add some characters
appendElement(tempList, 'H');
appendElement(tempList, 'e');
appendElement(tempList, 'l');
appendElement(tempList, 'l');
appendElement(tempList, 'o');
appendElement(tempList, '!');
printList(tempList);
//Step 3: Delete some charcters(the first occurence)
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
int main()
{
appendInsertDeleteTest();
return 0;
}// Of main
- 初始化
LinkList initLinkList();
单链表的第一个结点之前附加一个结点,称为头结点;
单链表的初始化操作就是申请一个头结点,将指针域置空
typedef struct LinkNode
{
char data;
struct LinkNode *next;
} LNode, *LinkList, *NodePtr;
定义了一个单链表的节点结构体,其中包含一个字符类型的数据成员和一个指向下一个节点的指针成员。同时,通过typedef
关键字,将该结构体类型定义为三个别名
:LNode、LinkList和NodePtr。
LNode
代表单链表的节点类型,可以用来定义单链表的节点变量
;
LinkList
代表单链表的类型,可以用来定义单链表的头指针变量
NodePtr
代表单链表节点指针类型,可以用来定义指向单链表节点的指针变量
。
-
遍历单链表
void printList(NodePtr paraHeader);
声明一个指针p,从头结点指向的第一个结点开始,如果p不为空,那么就输出当前结点的值,并将p指向下一个结点即p = p->next;,直到遍历到最后一个结点为止。 -
尾部添加元素
void appendElement(NodePtr paraHeader, char paraChar);
先创建一个结点,然后找到链表的尾部,然后将该结点添加到链表的尾部
在这个操作中不能忘了q->next = NULL;
该函数通过动态内存分配的方式创建了新节点q,并在单链表尾部添加了该节点。需要注意的是,在使用完动态分配的内存后,需要使用free函数将该内存释放,否则会造成内存泄漏。 -
插入元素
void insertElement(NodePtr paraHeader, char paraChar, int paraPosition);
从表头开始遍历,查找指定位置的前一个结点,即插入位置的前驱结点p
,然后令新结点q的指针域指向p的后继结点
,再令结点p的指针域指向新结点q。
核心代码:
q->next = p->next;
p->next = q;
- 删除元素
void deleteElement(NodePtr paraHeader, char paraChar);
函数首先从头节点开始遍历单链表,查找要删除的元素。具体来说,函数使用while循环
遍历单链表,直到找到一个节点,该节点的下一个节点的data成员等于paraChar或者遍历到了单链表的末尾为止。如果遍历到了单链表的末尾,则说明要删除的元素不存在,函数打印出错误信息并返回。否则,函数将要删除的节点q从单链表中删除,并用free
释放其占用的内存空间。
核心代码:
q = p->next;
p->next = p->next->next;
下面是测试单链表节点的地址:
/**
* Address test
*/
void basicAddressTest()
{
LNode 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
在该函数中,使用&运算符
获取了tempNode1和tempNode2的内存地址、数据成员地址和next指针地址,并通过printf函数打印出来。
分析:
第一个结点的地址以及其data的地址都是895415608
而结点的next则比895415608多了8,说明单链表中各个节点的内存地址一般会相差一个固定的值,这个值通常是节点结构体的大小(sizeof(LNode)).因为在单链表中每个节点都包含一个指向下一个节点的指针,所以每个节点在内存中的地址应该是连续的。在单链表中,每个节点包含一个数据成员和一个指向下一个节点的指针成员,因此一个节点在内存中的大小通常是8字节