从上一篇的博文中可见,线性表的顺序存储结构的特点是逻辑关系上相邻的两个元素在物理位置上也相邻,因此可以随机存取表中任一元素,它的存储位置可以用一个简单、直观的公式来表示。然而,从另一个方面来看,这个特点也铸成了这中存储结构的弱点:在进行插入和删除操作时,需要大量移动元素。这里介绍另一种线性表的表示方法—-链式存储结构,由于它不要求逻辑上相邻的元素在物理位置上也相邻,因此它没有顺序存储结构所具有的弱点,但同时也失去了顺序表可随机存取的优点。
一、线性链表
线性表的链式存储结构的特点是用一组任一的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。因此,为了表示每个数据元素a(i)与其直接后继数据元素a(i+1),对数据元素a(i)来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息。这两部分信息组成数据元素a(i)的存储映像,称为节点。它包括两个域:其中存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域。
1.单链表的存储结构
typedef struct Node
{
int data; //数据域
struct Node *next; //指针域
}Node, *List;
2.头结点、头指针和首节点
首结点是指链表中的第一个结点,它没有直接前驱;
头指针是指指向首结点的指针(没有头结点的情况下)。一个单链表可以由其头指针唯一确定,一般用其头指针来命名单链表。
头结点是在链表的开始结点之前附加的一个结点。有了头结点之后头指针指向头结点,不论链表是否为空,头指针总是非空,而且头结点的设置使得对链表的第一个位置上的操作与在表中其它位置上的操作一致
二、C语言实现单链表
1.单链表操作
在SList.h文件中声明单链表的操作
#ifndef SLIST_H_
#define SLIST_H_
typedef struct Node
{
int data; //数据域
struct Node *next; //指针域
}Node, *List;
Node* BuyNode(int val);
void InitList(List plist);//初始化链表
void HeadInsert(List plist, int val);//头插法
void TailInsert(List plist, int val);//尾插法
bool Delete(List plist, int key);//删除
void PrintList(List plist);//打印
int GetLength(List plist);//获取链表长度
Node *Search(List plist, int key);//查找
void Traverse(List plist); //倒置
void Destroy(List plist); //摧毁
#endif
2.单链表C代码实现
在SList.c文件中实现头文件中的操作
#include <stdio.h>
#include <stdlib.h>
#include "SList.h"
//初始化链表
void InitList(List plist)
{
if (NULL == plist)
{
printf("The list isn't exist.\n");
return;
}
plist->next = NULL;
}
//申请一个节点
Node* BuyNode(int val)
{
Node *p = (Node*)malloc(sizeof(Node));
p->next = NULL;
p->data = val;
return p;
}
//头插法
void HeadInsert(List plist, int val)
{
if (NULL == plist)
{
printf("The list is valid.\n");
return;
}
Node *p = BuyNode(val);
p->next = plist->next;
plist->next = p;
}
//尾插法
void TailInsert(List plist, int val)
{
if (NULL == plist)
{
printf("The list is valid.\n");
return;
}
//首先找最后一个节点
Node *pTail;
for (pTail = plist; pTail->next != NULL; pTail = pTail->next)
{
NULL;
}
Node *pNewNode = BuyNode(val);
pTail->next = pNewNode;
}
//删除
bool Delete(List plist, int key)
{
Node *pTemp = plist->next;
Node *pPrior = plist; //指向前驱节点
for (; pTemp != NULL; pTemp = pTemp->next, pPrior = pPrior->next)
{
if (pTemp->data == key)
{
pPrior->next = pTemp->next;
free(pTemp);
pTemp = NULL;
return true;
}
}
return false;
}
//打印
void PrintList(List plist)
{
Node *p;
for (p = plist->next; NULL != p; p = p->next)
{
printf("%5d", p->data);
}
printf("\n");
}
//获取链表长度
int GetLength(List plist)
{
Node *pTemp;
int iLen = 0;
for (pTemp = plist->next; pTemp != NULL; pTemp = pTemp->next)
{
++iLen;
}
return iLen;
}
//查找
Node *Search(List plist, int key)
{
Node *pTemp;
for (pTemp = plist->next; pTemp != NULL; pTemp = pTemp->next)
{
if (pTemp->data == key)
{
break;
}
}
return pTemp;
}
//倒置
void Traverse(List plist)
{
Node *pTemp = plist->next;
Node *pNext;
plist->next = NULL; //将头结点和后面的节点断开
for (; pTemp != NULL; pTemp = pNext)
{
pNext = pTemp->next;
pTemp->next = plist->next;
plist->next = pTemp;
}
}
//摧毁
void Destroy(List plist)
{
Node *pTemp;
while (plist->next != NULL)
{
pTemp = plist->next;
plist->next = pTemp->next;
free(pTemp);
}
}
int main(void)
{
Node head;
InitList(&head);
for (int i = 0; i < 12; ++i)
{
HeadInsert(&head, i);
}
PrintList(&head);
printf("the list length is %d\n.", GetLength(&head));
for (int i = 10; i > 0; --i)
{
TailInsert(&head, i);
}
PrintList(&head);
printf("the list length is %d\n.", GetLength(&head));
Delete(&head, 0);
PrintList(&head);
Traverse(&head);
PrintList(&head);
Destroy(&head);
PrintList(&head);
return 0;
}
小结一点,就是在上面这些函数中,有很多循环,有的循环变量初始化为plist,有的初始化为plist->next。有很多人老是搞错,不知道什么时候初始化为plist,什么时候初始化为plist->next,我在这里总结一点规律供大家参考记忆:单链表中凡是需要用到节点的前驱信息的操作均初始化为plist,如插入和删除;因为单链表的插入和删除,必须知道它的前驱节点我们才能进行正确的操作,不然会发生断链等问题。
4.单链表C++代码实现
SList_cpp.h文件:
#ifndef SLIST_CPP_H_
#define SLIST_CPP_H_
class CLink
{
private:
class CNode
{
public:
CNode()
{
_pNext = nullptr;
}
CNode(int iVal)
{
_iData = iVal;
_pNext = nullptr;
}
public:
int _iData;
CNode *_pNext;
};
public:
CLink();
~CLink();
void HeadInsert(int val); //头插法
void TailInsert(int val); //尾插法
CNode *Search(int key); //查找
CNode *SearchPri(int key); //查找前驱节点
bool Delete(int key); //删除
void PrintList(); //打印
int GetLength(); //获取链表长度
void Traverse(); //倒置
private:
CNode _pHead;
};
#endif
SList_cpp.cpp文件:
#include "SList_cpp.h"
#include <iostream>
using namespace std;
CLink::CLink()
{
_pHead._pNext = nullptr;
}
//释放所有节点,防止内存泄露
CLink::~CLink()
{
CNode *pTemp = _pHead._pNext;
while (NULL != pTemp)
{
_pHead._pNext = pTemp->_pNext;
delete pTemp;
pTemp = _pHead._pNext;
}
}
//头插法
void CLink::HeadInsert(int val)
{
CNode *pTemp = new CNode(val);
pTemp->_pNext = _pHead._pNext;
_pHead._pNext = pTemp;
}
//尾插法
void CLink::TailInsert(int val)
{
CNode *pTemp;
for (pTemp = &_pHead; pTemp->_pNext != NULL; pTemp = pTemp->_pNext)
{
NULL;
}
CNode *pNewNode = new CNode(val);
pTemp->_pNext = pNewNode;
}
//查找链表中第一个值为key的节点的前驱节点
CLink::CNode * CLink::SearchPri(int key)
{
CNode *pTemp;
CNode *pPrior = &_pHead;
for (pTemp = _pHead._pNext; pTemp != NULL; pPrior = pTemp, pTemp = pTemp->_pNext)
{
if (pTemp->_iData == key)
{
break;
}
}
return pPrior;
}
//查找链表中第一个值为key的节点
CLink::CNode * CLink::Search(int key)
{
CNode *pTemp = NULL;
for (pTemp = _pHead._pNext; pTemp != NULL; pTemp = pTemp->_pNext)
{
if (pTemp->_iData == key)
{
break;
}
}
return pTemp;
}
//删除值为key的节点
bool CLink::Delete(int key)
{
CNode *pPrior = SearchPri(key);
if (NULL != pPrior)
{
CNode *pCur = Search(key);
pPrior->_pNext = pCur->_pNext;
delete(pCur);
pCur = NULL;
return true;
}
return false;
}
//输出链表
void CLink::PrintList()
{
CNode *pTemp;
for (pTemp = _pHead._pNext; pTemp != NULL; pTemp = pTemp->_pNext)
{
cout << " " << pTemp->_iData;
}
cout << endl;
}
//获取链表长度
int CLink::GetLength()
{
int iCount = 0;
for (CNode *pTemp = _pHead._pNext; pTemp != NULL; pTemp = pTemp->_pNext)
{
++iCount;
}
return iCount;
}
//链表逆置
void CLink::Traverse()
{
CNode *pTemp = _pHead._pNext;
_pHead._pNext = NULL;
while (pTemp != NULL)
{
CNode *pNext = pTemp->_pNext;
pTemp->_pNext = _pHead._pNext;
_pHead._pNext = pTemp;
pTemp = pNext;
}
}
int main(void)
{
CLink link;
for (int i = 0; i < 10; ++i)
{
link.TailInsert(i);
}
link.PrintList();
cout << "length:" << link.GetLength() << endl;
link.Delete(5);
link.PrintList();
link.Traverse();
link.PrintList();
return 0;
}