数据结构--链表(c/c++)
一、线性表
线性表(linear list)属于数据结构中的线性结构,它是n个数据元素的有限序列,这里的元素含义各异,可以是一个数字、一个字符,也可以是更为复杂的信息。
(1)线性结构
线性结构的特点是:
- 只有一个头元素;
- 只有一个尾元素;
- 除头元素外,集合中每个数据元素只有一个前驱;
- 除尾元素外,集合中每个数据元素只有一个后继;
用通俗易懂的话来说就是你懂的!线性表也是这个特点。
(2)线性表的储存结构
线性表的储存结构包括顺序储存结构和链式储存结构。
顺序存储结构的特点是逻辑关系上相邻的元素在物理位置上也要相邻,因此它可以随机存取表中的任一元素。但是,这样的特性造成在进行插入或删除操作时,需要移动大量的元素。
链式储存结构的特点是用一组任意的储存单元储存数据元素,可以连续也可以不连续。使用链式储存结构的线性表就是我们常说的链表。
(3)链式储存结构—链表
为了表述链表中每个元素ai与其后继元素ai+1之间的逻辑关系,对ai来说,它需要储存本身的信息(数据域)以及指示后继位置的信息指针(指针域),这两部分加在一起成为结点。
1. 线性链表
上述链式储存结构的每个结点中只包含一个指针域,即指向后继元素的指针,故称之为线性链表或单链表。在单链表中也分为头结点为空(有空头链表)和头结点不为空(无空头链表)两种。这一章我们的代码主要围绕无空头链表!
1.1 结构体以及全局变量
#include <iostream>
#include <cstdlib> //malloc
using namespace std;
struct Node
{
int a; //数据域
struct Node *pNext; //指针域
};
//创建头尾节点
Node *pHead = NULL;
Node *pEnd = NULL;
为了便于增删改查的书写方便,我们将头尾结点设为全局变量。
1.2添加结点
添加结点有三种情况,分别是头添加、尾添加以及在链表任一位置添加。
//在链表的末端添加一个结点(尾添加)
void AddNodeToListTail(int a)
{
//创建新结点
Node *pTemp = (struct Node*)malloc(sizeof(Node));
//结点数据进行赋值
pTemp->a = a;
pTemp->pNext = NULL; //important 如果没有做这一步,极有可能使该指针未初始化(0xcdcdcdcd)
//链接
if (NULL == pHead || NULL == pEnd)
{
//如果该链表为空,则新节点既是头结点也是尾结点
pHead = pTemp;
//pEnd = pTemp;
}
else
{
//如多链表不空,则新结点指向尾节点的Next,再将尾结点移至最末
pEnd->pNext = pTemp;
//pEnd = pTemp; pEnd = pEnd->Next;
}
pEnd = pTemp;
}
//在链表的顶端添加一个结点(头添加)
void AddNodeToListHead(int a)
{
//创建新结点
Node * pTemp = (struct Node*)malloc(sizeof(Node));
//结点数据赋值
pTemp->a = a;
pTemp->pNext = NULL;
//链接
if(NULL == pHead)
{
//如果该链表为空,则新结点既是头结点也是尾结点
pHead = pTemp;
pEnd = pTemp;
}
else
{
//如果链表不空,则新结点的Next指向头结点,再将新结点设为头结点
pTemp->pNext = pHead;
pHead = pTemp;
}
}
/指定位置添加结点
void InsertNode(int index, int a)
{
//链表为空
if (NULL == pHead)
{
cout << "链表为空!" << endl;
return;
}
//找位置
Node *pt = SelectNode(index);
if (NULL == pt)
{
cout << "木得指定结点" << endl;
return;
}
//为a创建结点
Node *pTemp = (struct Node *)malloc(sizeof(Node));
//结点成员进行赋值
pTemp->a = a;
pTemp->pNext = NULL;
//链接
if (pt == pEnd)
{
//尾结点的Next指向新节点
pEnd->pNext = pTemp;
//新结点变成尾巴
pEnd = pTemp;
}
else
{
//先连
pTemp->pNext = pt->pNext;
//后断
pt->pNext = pTemp;
}
}
1.3遍历链表
遍历链表的操作分为遍历整个链表,我们通常使用此方法打印链表元素;以及查找某一结点,此功能多用于任一位置结点的添加与删除。
//遍历整个链表
void ScanTheList()
{
//定义中间指针记录头指针,不能使用pHead遍历,否则将会丢失头指针
Node * pTemp = pHead;
//bool变量的判断写成!temp,指针应该使用下列写法
while (pTemp != NULL)
{
cout << pTemp->a << endl;
pTemp = pTemp->pNext;
}
}
//查询指定结点
struct Node* SelectNode(int a)
{
Node * pTemp = pHead;
while ( pTemp != NULL)
{
if (a == pTemp->a)
{
return pTemp;
}
pTemp = pTemp->pNext;
}
//未找到
return NULL;
}
因为在单链表中,任何两个元素的储存位置没有固定的联系,所以想找到第i个元素就必须从头结点出发寻找,若有n个结点,最坏情况要遍历并比较n次,所以它的时间复杂度为O(n)。
1.4删除链表中的结点
//(头删除)
void DeleteListHead()
{
//链表检测
if (NULL == pHead)
{
cout << "链表为空!" << endl;
}
//不可以直接free(pHead)
Node *pt = pHead;
pHead = pHead->pNext;
free(pt);
}
//(尾删除)
void DeleteListTail()
{
//链表检测
if (NULL == pHead)
{
cout << "链表为空!" << endl;
return;
}
//判断结点数
if(pHead == pEnd)
{
free(pHead);
pHead = NULL;
pEnd = NULL;
}
else //多结点
{
//
Node *pTemp = pHead;
while (pTemp->pNext != pEnd)
{
pTemp = pTemp->pNext;
}
//删除尾结点
free(pEnd);
pEnd = pTemp;
pEnd->pNext = NULL;
}
}
//删除指定结点
void DelectRandNode(int a)
{
//链表检测
if (NULL == pHead)
{
cout << "链表为空!" << endl;
return;
}
Node *pTemp = SelectNode(a);
if (NULL == pTemp)
{
cout << "查无此结点!" << endl;
return;
}
//对结点数量讨论
if (pEnd == pHead) //一个
{
//DeleteListHead();
free(pHead);
pHead = NULL;
pEnd = NULL;
}
else if (pHead->pNext == pEnd) //两个
{
if (pTemp == pHead)
{
DeleteListHead();
}
else if (pTemp == pEnd)
{
DeleteListTail();
}
}
else //多个
{
if (pTemp == pHead)
{
DeleteListHead();
}
else if (pTemp == pEnd)
{
DeleteListTail();
}
else
{
Node *pt = pHead;
while ( pt->pNext != pTemp )
{
pt = pt->pNext;
}
pt->pNext = pTemp->pNext;
free(pTemp);
}
}
}
删除结点和遍历结点类似,时间复杂度为O(n)。
1.5释放整个链表空间
为了防止内存泄漏,我们需要在使用结束后将链表清空。
//链表清空,防止内存泄漏
void FreeList()
{
Node * pTemp = pHead;
while ( pTemp != NULL)
{
Node * pt = pTemp; //禁止在此处使用free(pTemp)
pTemp = pTemp->pNext;
free(pt);
}
//清空头尾结点
pHead = NULL;
pEnd = NULL;
}
1.6注意事项
全部都写在了注释里哦!!!
完整代码
//无空头链表的操作
#include <iostream>
#include <cstdlib> //malloc
using namespace std;
struct Node
{
int a;
struct Node *pNext;
};
//创建头尾节点
Node *pHead = NULL;
Node *pEnd = NULL;
//链表清空
void FreeList();
//在链表的末端添加一个节点(尾添加)
void AddNodeToListTail(int a);
//在链表的顶端添加一个节点(头添加)
void AddNodeToListHead(int a);
//指定位置添加节点
void InsertNode(int index, int a);
//遍历链表 查全部、查指定
void ScanTheList();
//查询指定节点
struct Node* SelectNode(int a);
//(头删除)
void DeleteListHead();
//(尾删除)
void DeleteListTail();
//(删除指定节点)
void DelectRandNode(int a);
//栈 头添加+头删除
//队列 头添加+尾删除/头删除+尾添加
int main()
{
int a[10] = {1,2,3,4,5};
//pHead;
int i = 0;
for( ; i < 10; i++ )
{
AddNodeToListTail(a[i]);
}
//ScanTheList();
Node *pFind = SelectNode(15);
if( pFind != NULL )
{
cout << pFind->a << endl;
}
else
{
cout << "没找到" <<endl;
}
cout << "Hello world!" << endl;
system("pause");
return 0;
}
//链表清空,防止内存泄露
void FreeList()
{
Node * pTemp = pHead;
while ( pTemp != NULL)
{
Node * pt = pTemp; //禁止在此处使用free(pTemp)
pTemp = pTemp->pNext;
free(pt);
}
//清空头尾节点
pHead = NULL;
pEnd = NULL;
}
//在链表的末端添加一个节点(尾添加)
void AddNodeToListTail(int a)
{
//创建一个节点
Node *pTemp = (struct Node*)malloc(sizeof(Node));
//节点数据进行赋值
pTemp->a = a;
pTemp->pNext = NULL; //important 如果没有做这一步,极有可能使该指针未初始化(0xcdcdcdcd)
//链接
if (NULL == pHead || NULL == pEnd)
{
//如果该链表为空,则新节点既是头节点也是尾节点
pHead = pTemp;
//pEnd = pTemp;
}
else
{
//如多链表不空,则新节点指向尾节点的Next,再将尾节点移至最末
pEnd->pNext = pTemp;
//pEnd = pTemp; pEnd = pEnd->Next;
}
pEnd = pTemp;
}
//在链表的顶端添加一个节点(头添加)
void AddNodeToListHead(int a)
{
//创建新节点
Node * pTemp = (struct Node*)malloc(sizeof(Node));
//节点数据赋值
pTemp->a = a;
pTemp->pNext = NULL;
//链接
if(NULL == pHead)
{
//如果该链表为空,则新节点既是头节点也是尾节点
pHead = pTemp;
pEnd = pTemp;
}
else
{
//如果链表不空,则新节点的Next指向头结点,再将新节点设为头结点
pTemp->pNext = pHead;
pHead = pTemp;
}
}
//指定位置添加节点
void InsertNode(int index, int a)
{
//链表为空
if (NULL == pHead)
{
cout << "链表为空!" << endl;
return;
}
//找位置
Node *pt = SelectNode(index);
if (NULL == pt)
{
cout << "木得指定节点" << endl;
return;
}
//为a创建节点
Node *pTemp = (struct Node *)malloc(sizeof(Node));
//节点成员进行赋值
pTemp->a = a;
pTemp->pNext = NULL;
//链接
if (pt == pEnd)
{
//尾节点的Next指向新节点
pEnd->pNext = pTemp;
//新节点变成尾巴
pEnd = pTemp;
}
else
{
//先连
pTemp->pNext = pt->pNext;
//后短
pt->pNext = pTemp;
}
}
//遍历整个链表
void ScanTheList()
{
//定义中间指针记录头指针,不能使用pHead遍历,否则将会丢失头指针
Node * pTemp = pHead;
//bool变量的判断写成!temp,指针应该使用下列写法
while (pTemp != NULL)
{
cout << pTemp->a << endl;
pTemp = pTemp->pNext;
}
}
//查询指定结点
struct Node* SelectNode(int a)
{
Node * pTemp = pHead;
while ( pTemp != NULL)
{
if (a == pTemp->a)
{
return pTemp;
}
pTemp = pTemp->pNext;
}
//未找到
return NULL;
}
//(头删除)
void DeleteListHead()
{
//链表检测
if (NULL == pHead)
{
cout << "链表为空!" << endl;
}
//不可以直接free(pHead)
Node *pt = pHead;
pHead = pHead->pNext;
free(pt);
}
//(尾删除)
void DeleteListTail()
{
//链表检测
if (NULL == pHead)
{
cout << "链表为空!" << endl;
return;
}
//判断节点数
if(pHead == pEnd)
{
free(pHead);
pHead = NULL;
pEnd = NULL;
}
else //多节点
{
//
Node *pTemp = pHead;
while (pTemp->pNext != pEnd)
{
pTemp = pTemp->pNext;
}
//删除尾节点
free(pEnd);
pEnd = pTemp;
pEnd->pNext = NULL;
}
}
//删除指定节点
void DelectRandNode(int a)
{
//链表检测
if (NULL == pHead)
{
cout << "链表为空!" << endl;
return;
}
Node *pTemp = SelectNode(a);
if (NULL == pTemp)
{
cout << "查无此节点!" << endl;
return;
}
//对节点数量讨论
if (pEnd == pHead) //一个
{
//DeleteListHead();
free(pHead);
pHead = NULL;
pEnd = NULL;
}
else if (pHead->pNext == pEnd) //两个
{
if (pTemp == pHead)
{
DeleteListHead();
}
else if (pTemp == pEnd)
{
DeleteListTail();
}
}
else //多个
{
if (pTemp == pHead)
{
DeleteListHead();
}
else if (pTemp == pEnd)
{
DeleteListTail();
}
else
{
Node *pt = pHead;
while ( pt->pNext != pTemp )
{
pt = pt->pNext;
}
pt->pNext = pTemp->pNext;
free(pTemp);
}
}
}