链表
数组是程序设计语言所提供的一种非常特殊有用的数据结构,但是,数组至少有两个局限:(1)编译器就要知道大小;(2)数组中的数据在计算机内存中是以相同距离间隔开的,这意味着要在数组中插入一个数据,需要移动该数组中的其它数据。链表就不存在这些问题,链表是节点的集合,节点中存储着数据并连接到到其它的节点。通过这种方式,节点可以在内存中的任何位置,每个节点都存储着链表中其它节点的地址,因此数据很容易从一个节点到达另一个节点。链表的实现方式有很多种,但最灵活的实现方法是使用指针。
单链表
如果一个节点将指向另一个节点的指针作为数据成员,那么多个这样的节点可以连接起来,只用一个变量就能够访问整个节点序列。这样的节点序列就是最常用的链表的实现方法。链表是一种由节点组成的数据结构,每个节点都包含某些信息以及指向链表中另一个节点的指针。如果序列中的节点只包含指向后继节点的链接,该链表就成为单向链表。有时我们在单链表的第一个结点之前附设一个节点,称之为头节点。头节点的数据域可以不存储任何信息,也可以存储如线性表的长度等类的附加信息,头节点的指针域存储指向第一个节点的指针。
(a)非空表;(b)空表
单循环链表
循环链表时另一种形式的链式存储结构。它的特点时表中最后一个节点的指针指向头节点,整个链表形成一个环。由此,从表中任一个节点出发均可找到其它节点。如图:
(a)非空表;(b)空表
循环链表的操作和线性表基本一致,差别仅在于算法中的循环条件不是p或者p->next是否为空,而是它们是否等于头指针。但有的时候,若再循环链表中设立尾指针而不设头指针,如图:
这样可使某些操作简化。例如将两个线性(比如上图)表合并成一个表时,仅需要将一个表的尾指针和另一个表的表头相接。如图,这个操作仅需要改变两个指针即可,运行时间为O(1)。如下图:
源码实现
几个主要的功能:
- 初始化:创建带有头节点的链表
- 尾插法建立单链表
- 头插法建立单链表
- 求长度:求线性表中的节点的个数
- 取节点:取给定位置的节点
- 查节点:查找给定数据的节点的位置
- 插入节点:在指定的节点之后插入节点
- 插入节点:在指定的节点之前插入节点
- 删除节点:删除指定节点的后继节点
- 删除节点:删除第i个节点
- 遍历节点:从头到尾扫描线性表
- 单链表的反转
源码:
#include <stdio.h>
#include <stdlib.h>
// 元素类型
typedef int ElemType;
// 定义节点
typedef struct node{
ElemType data;
struct node *pNext;
}LinkListNode;
/*
* 初始化: 创建带有头节点的链表
* 返回值: 返回创建好的头节点
*/
LinkListNode *initLinkList()
{
LinkListNode *pHead = NULL;
pHead = (LinkListNode *)malloc(sizeof(LinkListNode));
if (pHead != NULL)
pHead->pNext = NULL;
return pHead;
}
/*
* 尾插法建立单链表
* 参数: arr:传入的顺序表,length:顺序表的长度
* 返回值: 返回链表的头节点的指针
*/
LinkListNode *createTailLinkList(ElemType arr[], int length)
{
if (length <= 0)
return NULL;
LinkListNode *pHead, *p, *q;
pHead = (LinkListNode *)malloc(sizeof(LinkListNode));
q = pHead;
for (int i = 0; i < length; ++i) {
p = (LinkListNode *)malloc(sizeof(LinkListNode));
p->data = arr[i];
q->pNext = p;
q = p;
}
p->pNext = NULL;
return pHead;
}
/*
* 头插法建立单链表
* 参数: arr:传入的顺序表,length:顺序表的长度
* 返回值: 返回链表的头节点的指针
*/
LinkListNode *createHeadLinkList(ElemType arr[], int length)
{
if (length <= 0)
return NULL;
//p是新加入节点,q是当前节点
LinkListNode* pHead, *p, *q;
int i;
q = NULL;
for (i = length - 1;i >= 0;i--) {
p = (LinkListNode*)malloc(sizeof(LinkListNode));
p->data = arr[i];
p->pNext = q;
q = p;
}
pHead = (LinkListNode*)malloc(sizeof(LinkListNode));
pHead->pNext = q;
return pHead;
}
/*
* 求长度: 求线性表中的节点的个数
* 参数: pHead,链表的头指针
* 返回值: 链表的节点数量。错误时返回-1
*/
int getSizeOfLinkList(LinkListNode *pHead)
{
if (pHead == NULL)
return -1;
int cnt = 0;
while (pHead->pNext) {
++cnt;
pHead = pHead->pNext;
}
return cnt;
}
/*
* 取节点: 取给定位置的节点
* 参数: pHead,链表的头指针;pos,节点的位置
* 返回值: 找到,返回节点的指针,否则返回NULL
*/
LinkListNode *getLinkListNode(LinkListNode *pHead, int pos)
{
if (pHead == NULL || pos <= 0)
return NULL;
int curPos = 0;
LinkListNode *p = pHead;
while (p->pNext != NULL) {
p = p->pNext;
if (curPos == pos)
return p;
++curPos;
}
return NULL;
}
/*
* 查节点: 查找给定数据节点的位置
* 参数: pHead,链表的头指针;elem,节点的元素数据
* 返回值: 找到,返回节点的指针,否则返回NULL
*/
LinkListNode *locateLinkList(LinkListNode *pHead, ElemType objData)
{
if (pHead == NULL)
return NULL;
LinkListNode *p = pHead->pNext;
while (p->data != objData && p->pNext != NULL) {
p = p->pNext;
}
if (p->data == objData)
return p;
else
return NULL;
}
/*
* 插入节点:在指定的节点之后插入节点
* 参数: ptr,要插入的节点的位置的节点;elem,节点的元素数据
*/
void insertAfterNode(LinkListNode *ptr, ElemType elem)
{
if (ptr == NULL)
return;
LinkListNode *p = (LinkListNode *)malloc(sizeof(LinkListNode));
p->pNext = ptr->pNext;
p->data = elem;
ptr->pNext = p;
}
/*
* 插入节点:在指定的节点之前插入节点
* 参数: pHead,链表的头指针;ptr,要插入的节点的位置的节点;elem,节点的元素数据
*/
void insertBeforeNode(LinkListNode* pHead, LinkListNode *ptr, ElemType elem)
{
if (ptr == NULL || pHead == NULL)
return;
LinkListNode *p = (LinkListNode *)malloc(sizeof(LinkListNode));
p->pNext = ptr;
p->data = elem;
LinkListNode *q = pHead;
while (q->pNext != ptr) {
q = q->pNext;
}
q->pNext = p;
}
/*
* 删除节点:删除指定节点的后继节点
* 参数: ptr,删除ptr节点之后的节点
* 返回值: 返回删除的节点
* 注意: 删除返回目标节点的地址,并不涉及到动态空间的回收
* 在动态回收空间的要求中,应该遵循的原理是谁污染谁治理
* 在顺序表中的删除就是逻辑上的删除,就是说我们的这个节点不再
* 存在于当前的顺序表中了
*/
LinkListNode *deleteAfterLkList(LinkListNode *ptr)
{
LinkListNode *fptr;
fptr = ptr->pNext;
ptr->pNext = fptr->pNext;
return fptr;
}
/*
* 删除节点:删除第i个节点
* 参数: pHead,链表的头指针;i,第i个节点
* 返回值: 删除成功返回删除的节点指针,否则返回NULL
*/
LinkListNode* Delete_i_LkList(LinkListNode* pHead, int i)
{
if (i < 0 || pHead == NULL)
return NULL;
LinkListNode*ptr, *qPtr = NULL;
ptr = getLinkListNode(pHead, i - 1);//找到i的前继节点
if (ptr != NULL && ptr->pNext != NULL)
qPtr = deleteAfterLkList(ptr);
return qPtr;
}
/*
* 遍历节点
* 参数: pHead,链表的头指针
*/
void showLkList(LinkListNode* pHead)
{
if (pHead == NULL)
return;
LinkListNode* p = pHead->pNext;
while (p != NULL) {
printf(" %d", p->data);
p = p->pNext;
}
}
/*
* 单链表的反转
* 参数: pHead,传入链表的头指针
* 大体思路:首先定义三个LinkListNode指针变量Front指向头指针,Cur指向第一个节点,Next指向第一个节点的下一个节点,
* 然后Cur的成员变量pNext指向Front,接着Front指向Cur,Cur再指向Next,依次类推挨个节点指向前一个节点。
*/
LinkListNode *LinkListReverse(LinkListNode *pHead)
{
if (pHead == NULL || pHead == NULL)
return NULL;
if (pHead->pNext->pNext == NULL)
return pHead;
LinkListNode *Front = NULL, *Cur = NULL, *Next = NULL;
Front = pHead;
Cur = Front->pNext;
Next = Cur->pNext;
Cur->pNext = Front;
Front = Cur;
Cur = Next;
while (Cur->pNext != NULL) {
Next = Cur->pNext;
Cur->pNext = Front;
Front = Cur;
Cur = Next;
}
// 当进行到最后一个节点的时候Cur->pNext == NULL,所以跳出循环,
// 但是此时Cur->pNext还是NULL,需要Next = Cur->pNext;
Cur->pNext = Front;
// pHead一直未进行操作,所以此时的pHead->pNext指向的是第一个节点
// 反转完成之后第一个节点变成了最后一个节点,所以取值为NULL
pHead->pNext->pNext = NULL;
pHead->pNext = Cur;
return pHead;
}
int main()
{
ElemType MySeq[] = { 1,2,3,4,5 };
LinkListNode* pHead1 = createHeadLinkList(MySeq, 5);
insertBeforeNode(pHead1, locateLinkList(pHead1, 2), 111);
LinkListNode* pHead2 = createTailLinkList(MySeq, 5);
getchar();
return 0;
}
方法不太全,大家可以根据自己的需求进行扩展。