目录
一、线性表链式存储结构
1.1链式存储结构的定义
1、线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的也可及时布偶连续的。即这些数据元素可以存储在内存未被占据的任意位置。
2、以前的顺序结构中,每个元素只需要存放数据元素就可以了。现在链式结构中,除了要存放数据元素的信息外,还要存储它的后继元素的存储地址。我们把存储数据元素信息的区域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称为指针或链。这两部分信息组成一个数据元素的存储映像,称为结点(Node)。
3、对于线性表来说,总得有头有尾,链表也不例外。我们把链表中第一个结点的存储位置叫做头指针,整个链表的存取必须是从头指针开始进行。之后的每个结点,其实都是就是上一个的后继指针指向的位置。对于最后一个结点,它的直接后继就不存在了,所以我们规定,线性链表的最后一个结点指针为“空”(通常用NULL或“^”表示)。
4、为了更加方便的对链表进行操作,会在单链表的第一个结点前附设一个结点为头结点。头结点的数据域可以不存储任何信息,也可以存储线性表长度等附加信息,头结点的指针域存放指向第一个结点的指针。
1.2头指针和头结点的异同
- 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针
- 头指针具有标识作用,所以常用头指针冠以链表的名字
- 无论链表是否为空,头指针均不为空。头指针是链表的必要元素
- 头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义(也可以存放链表的长度)
- 有了头结点,对在第一元素之前插入结点和删除第一结点,其操作与其它结点的操作就统一了
- 头结点不一定是链表必须要素
二、线性链表的C代码描述
2.1存储结点的定义
#include<stdio.h>
#define OK 1
#define TURE 1
#define FALSE -1
typedef int Status;
typedef int ElemType;
typedef struct Node{
ElemType data; //数据域
struct Node *next; //指针域
}Node,*LinkList;
2.2链表的初始化
//初始化链表
Status ListInit(LinkList L)
{
L=(LinkList)malloc(sizeof(Node));
L->next=NULL;
return OK;
}
2.3链表的插入
对于单链表的插入操作,遵循“先链后断”的原则。也就是说要在a1结点和a2结点之间插入一个新的s结点,那么我们应该先将s结点的指针域指向a2结点即(s->next = a1->next)再断开a1与a2即a1的指针域重新指向s(a1->next = s)。反之如果先断后链,则会出现断链的情况,也就是是说如果先将a1结点与其后继结点a2断开,并将a1的指针域指向了新结点s,那么s结点的指针就找不到其后继结点a2。了因为之前存放a2结点的指针已经被s结点的地址所覆盖。
单链表第i个数据插入结点的算法思路:
- 声明一个指针p指向链表的表头结点,初始化j从1开始
- 当 j < i 时。就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;
- 若到链表末尾p为空,则说明第i个结点不存在;
- 否则查找成功,在系统中生成一个空结点s;
- 将数据元素e赋值给s->data;
- 单链表的插入标准语句s->data = p->next; p->next = s;
- 返回成功。
Status ListInsert(LinkList *L,int i,Elemtype e)
{
//在单链表L中第i个位置插入e
Node *p,*s;
int j=1;
p=*L;
while(p!=NULL && j<i)
{
//找到i-1个结点
p=p->next;
++j;
}
if(!p || j >i)
return FALSE;//未找到插入位置,出错处理
s=(LinkList)malloc(sizeof(Node));//生成新结点
s->data=e;
s->next=p->next;//插入新结点
p->next=s;
return OK;
}
2.4单链表的创建
2.4.1.采用头插法创建单链表
该方法是从一个空表开始,生成新结点,并将读取到的数据放到新结点的数据域中,然后将新结点插入到当前链表的表头,即头结点之后。整体思路如下:
- 声明一个指针p和计数变量i;
- 初始化一个空链表L;
- 让L的头结点的指针指向NULL,即创建一个带头结点的单链表;
- 循环:
- 生成一新结点赋值给p;
- 将要插入的数据赋给p的数据域p->data;
- 将p插入到头结点与前一新结点之间。
//头部插入法,不用返回头指针的形式
void CreateList(LinkList *L,int n)
{
int i;
Node *p;
*L=(LinkList)malloc(sizeof(Node));
(*L)->next=NULL;
for(i=n;i>0;i--)
{
p=(LinkList)malloc(sizeof(Node));
printf("No.%d:",i);
scanf("%d",&p->data);
p->next=(*L)->next;
(*L)->next=p;
}
}
//创建一个完整的单链表,头插法,返回头指针
LinkList CreatList_Head(LinkList L,int n)
{
LinkList p;
int i=0;
L = (LinkList)malloc(sizeof(Node));
L->next =NULL;
for(i =0; i<n;i++)
{
p =(LinkList)malloc(sizeof(Node));
printf("请输入第%d个元素的值:" ,i+1);
scanf("%d",&p->data);
p->next = L->next;
L->next = p;
}
return L; //返回头指针
}
2.4.2.采用尾插法创建单链表
除了像上面那要,前每一个插入的结点作为头结点的直接后继结点之外,我们为什么不把新结点都放在最后呢,这才是排队先来后到的正常思维。我们把每次新结点都插在终端结点的后面,这种算法称之为尾插法。
void CreatListTail(LinkList *L, int n)
{
LinkList p,r;//定义两个指针p存放新结点,r作为辅助结点一直指向当前链表的尾结点
int i=0;
*L= (LinkList)malloc(sizeof(Node));
(*L)->next = NULL; //建立一个头结点
r = *L;
for(i = 0 ; i < n; i++)
{
p=(LinkList)malloc(sizeof(Node));
printf("No.%d:",i+1);
scanf("%d",&p->data);
p->next = r->next; // 将新结点的指针指向尾结点的后继结点即NULL
r->next = p; //将尾结点的指针指向新的结点
r = p; //将当前的新结点定义为终端结点
}
}
2.5单链表的显示
定义一个辅助指针p从头结点开始一次遍历其后续结点,直到p为NULL,即遍历结束。其实现代码如下:
void ShowList(LinkList L)
{
LinkList p;
p = L;
while(p->next!=NULL)
{
p = p->next;
printf(" %d",p->data);
}
printf("\n");
}
2.6单链表的读取
在线性表的顺序存储结构中我们要计算人一个元素的存储位置是很容易的。但在单链表中,由于第i个元素在哪里没办法一开始就知道,必须从头结点开始依次寻找。
获得链表第 i 个数据的算法思路:
- 声明一个指针p指向链表的第一个结点,初始化 j 从1开始;
- 当 j<i时,就遍历链表,让p的指针向后移动,不断指向一个结点,j 累加1;
- 若到链表末尾p为空,则说明第i个结点不存在;
- 否则查找成功,返回结点p的数据。
Status GetElem(LinkList L,int i, ElemType *e)
{
LinkList p;
int j =1; //j为计数器
p = L->next;
while( p && j < i) //p不为空且计数器j不等于i 则循环继续
{
p = p->next;
++j;
}
if( !p || j > i) //第i个结点不存在
return FASLE;
*e = p->data;
return OK;
}
2.7单链表的删除
2.7.1删除单链表的第i个元素
单链表的删除类似单链表的读取我们要找到第i个数据然后,对其进行删除操作。其算法思路如下:
- 声明一个指针p指向头结点,初始化j从1开始;
- 当 j < i 时,就遍历链表,让p指针向后移动,不断指向下一个结点,j累加1;
- 若到链表末尾p为空,则说明第i个结点不存在;
- 否则查找成功,将要删除的结点p->next赋值给q;
- 单链表的删除标准语句是: p->next = q->next;
- 将q结点中的数据赋值给e,作为返回;
- 释放q结点;
- 返回成功。
Status ListDelete(LinkList *L,int i, ElemType *e)
{
int j = 1;
LinkList p,q;
p = *L;
while(p->next && j < i) //遍历寻找第i-1个结点
{
p = p->next;
++j;
}
if(p->next == NULL || j > i)
return FALSE; // 第i个结点不存在
q = p->next;
p->next = q->next;
*e = q->data;
free(q);
return OK;
}
2.7.1单链表整表的删除
单链表整表删除的算法思路如下:
- 声明两个结点p和q;
- 将第一个结点赋值给p;
- 循环:
-
将下一个结点赋值给q;
-
释放p;
-
将q赋值给p。
Status ClearList(LinkList *L)
{
LinkList p,q;
p = (*L)->next; //p指向第一个结点
while(p)
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;
return OK;
}
三、线性链表的C++代码描述
整体算法思想与c代码是一致的,所以就直接给出代码以供参考
3.1 LinkList.h代码如下:
#include<iostream>
using namespace std;
typedef int ElemType;
//构建一个节点类
class Node{
public:
ElemType data; //数据域
Node *next; //指针域
};
//构建一个链表类
class LinkList{
public:
LinkList(); //构建一个单链表;
~LinkList(); //销毁一个单链表;
void CreateLinkList_Head(int n); //创建一个单链表(头插法)
void CreateLinkList_End(int n); //创建一个单链表尾插法
void TravalLinkList(); //遍历线性表
int GetLength(); //获取线性表长度
bool IsEmpty(); //判断单链表是否为空
ElemType Find(int n); //查找节点
void InsertElemAtEnd(ElemType data); //在尾部插入指定的元素
void InsertElemAtIndex(ElemType data,int n); //在指定位置插入指定元素
void InsertElemAtHead(ElemType data); //在头部插入指定元素
void DeleteElemAtEnd(); //在尾部删除元素
void DeleteAll(); //删除所有数据
void DeleteElemAtPoint(ElemType data); //删除指定的数据
void DeleteElemAtHead(); //在头部删除节点
private:
Node *head; //头结点
};
3.2 LinkList.cpp如下所示:
#include"LinkList.h"
//初始化单链表
LinkList::LinkList()
{
head = new Node;
head->data = 0;
head->next = NULL;
}
LinkList::~LinkList()
{
delete head; // 删除头结点
}
//采用头插法创建一个链表
void LinkList::CreateLinkList_Head(int n)
{
Node *p,*H;
H = head;
if(n < 0)
cout<<"输入的结点个数有误!!!"<<endl;
for(int i=0;i < n ;i++)
{
p = new Node;
cout<<"请输入data"<<i+1<<"的值:";
cin >> p->data;
p->next = H->next;
H->next =p;
}
}
//采用尾插法构建一个单链表
void LinkList::CreateLinkList_End(int n)
{
Node *p,*s; // 定义两个辅助指针
s = head;
if(n < 0)
cout<<"输入的结点个数有误!!!"<<endl;
for(int i=0;i < n ;i++)
{
p = new Node;
cout<<"请输入data"<<i+1<<"的值:";
cin >> p->data;
p->next = s->next; //新节点的下一个地址为NULL
s->next = p; //当前结点的下一个地址设为新节点
s = p; //将当前结点设为尾结点
}
}
int LinkList::GetLength()
{
int count=0;
Node *p;
p = head;
while(p ->next)
{
p = p->next;
count++;
}
return count;
}
bool LinkList::IsEmpty()
{
if(head->next == NULL)
return true;
else
return false;
}
ElemType LinkList::Find(int n)
{
Node*p;
p = head;
int i=1;
if( n < 1 || n > this->GetLength() ) // 判断查找位置是否合法
{
cout<<"您输入的查找位置不合法";
return false;
}
else
{
while(p->next != NULL && i < n)
{
p=p->next;
i++;
}
return ((p->next)->data);
}
}
void LinkList::TravalLinkList()
{
if (head == NULL || head->next == NULL)
{
cout<<"链表为空!!"<<endl;
}
Node *p = head;
while(p->next != NULL)
{
p = p->next;
cout<< p->data << " ";
}
}
//在尾部插入指定的元素
void LinkList::InsertElemAtEnd(ElemType data)
{
Node *p,*s;
s = new Node;
s->data = data;
p = head;
while(p->next)
{
p = p->next;
}
p->next = s;
s->next =NULL;
}
//在指定位置插入元素
void LinkList::InsertElemAtIndex(ElemType data,int n)
{
Node *p,*s;
s = new Node; //创建要插入的结点
s->data = data;
p = head;
int i=1;
if( n < 1 || n > this->GetLength()+1 ) // 判断插入元素的位置是否合法
{
cout<<"您插入元素的位置不合法";
delete s;
}
else
{
while(p->next != NULL && i < n) // 找到要插入位置的前一个位置
{
p=p->next;
i++;
}
s->next = p->next; //插入该元素
p->next = s;
}
}
//在头部插入指定元素
void LinkList::InsertElemAtHead(ElemType data)
{
Node *p,*s;
s = new Node;
s->data = data;
p = head;
s->next = p->next;
p->next = s;
}
//在尾部删除元素
void LinkList::DeleteElemAtEnd()
{
Node*p =head; //创建一个指针指向头结点
Node*ptemp = NULL; //创建一个占位节点
if (p->next == NULL)
cout<<"链表为空"<<endl;
else{
while(p->next!=NULL) //循环到尾部的前一个
{
ptemp = p; // 将ptemp指向尾部的前一个结点
p = p->next; // p 指向最后一个结点
}
delete p;
p = NULL;
ptemp->next =NULL;
}
}
//删除所有元素
void LinkList::DeleteAll()
{
Node* p = head->next;
Node*ptemp;
while (p != NULL) //在头结点的下一个节点逐个删除节点
{
ptemp = p;
p = p->next;
head->next = p;
ptemp->next = NULL;
delete ptemp;
}
head->next = NULL; //头结点的下一个节点指向NULL
}
//删除链表中指定的数据
void LinkList::DeleteElemAtPoint(ElemType data)
{
Node *p = head;
if(p->next == NULL)
{
cout <<"链表为空!!"<<endl;
}
else
{
while(p->next != NULL)
{
if(p->next->data == data)
{
Node *temp = p->next; //定义一个辅助指针保存要删除的结点
p->next = temp->next; //删除该结点
delete temp;
}
else
p = p->next;
}
}
}
3.3 main.cpp如下所示:
#include <iostream>
#include"LinkList.h"
int main() {
LinkList L;
int n;
int data;
L.CreateLinkList_End(5);
cout<<"您创建的链表为:";
L.TravalLinkList();
cout<<"该链表的长度为:"<<L.GetLength()<<endl;
cout<<"请输入你要查找的元素序号:";
cin>>n;
cout<<"该链表第"<<n<<"个元素为:"<< L.Find(n)<<endl;
L.InsertElemAtEnd(11);
cout<<"追加元素后的链表为:";
L.TravalLinkList();
cout<<endl;
L.InsertElemAtIndex(50,2);
L.TravalLinkList();
cout<<endl;
L.DeleteElemAtEnd();
L.TravalLinkList();
cout<<"请输入你要删除的元素:";
cin >> data;
L.DeleteElemAtPoint( data);
L.TravalLinkList();
cout<<"清空后的链表为:";
L.DeleteAll();
L.TravalLinkList();
return 0;
}
3.4测试结果如下:
四:单链表顺序存
储结构与链式存储结构优缺点对比:
4.1存储分配方式:
顺序存储采用一段连续的存储单元依次存储线性表的数据元素。
单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素。
4.2 时间性能
查找:
顺序存储结构O(1) 单链表 O(n)
插入与删除:
顺序存储结构需要平均移动表长一半的元素,时间为O(n) 单链表在找到某位置的指针后,插入和删除时间仅为O(1)
4.3 空间性能:
顺序存储结构需要预先分配存储空间,太大了,浪费,太小了容易发生溢出。
单链表不需要预先分配存储空间,只要有就可以分配,元素个数也不受限制。
通过上述对比,我们可以得出一些结论:
如果线性表需要频繁的查找,很少进行插入和删除操作时,宜采用顺序存储结构。若需要频繁的插入和删除操作时,宜采用单链表结构。
当线性表中的元素个数变化较大或者根本不知道有多大时,最好采用单链表结构,从而不用考虑存储空间的问题。而如果事先知道线性表的大致长度,用线性存储结构效率会更高。