数据结构学习——线性表的链式表示及实现--单链表
线性表的链式表示及实现
基本概念
与链式存储有关的术语
结点:数据元素的存储映像。由数据域和指针域两部分组成
链表:n个结点由指针链组成的一个链表
它是线性表的链式存储映像,称为线性表的链式存储结构
单链表、双链表、循环链表:
结点只有一个指针域的链表,称为单链表或线性链表
结点有两个指针域的链表,称为双链表
首尾相接的链表称为循环链表
头指针、头结点和首元结点: 重要概念要理解清楚
头指针:是指向链表中第一个结点的指针
首元结点:是指链表中存储第一个数据元素a1的结点
头结点:是在链表的首元结点之前附设的一个结点
链表(链式存储结构)的特点
1.结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
2.访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间不等,即存取元素的方法为顺序存取法
链表的存储结构两种形式
一、不带头结点
表示空表:头指针为空时表示空表
二、带头结点(使用这种)
表示空表:当头结点的指针域为空时表示空表
设置头结点的好处:
1.便于首元结点的处理
首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置上的操作和其他位置一致,无需进行特殊处理
2.便于空表和非空表的统一处理
无论链表是否为空,头指针都是指向头结点的非空指针,因此空表和非空表的处理也就统一了
头结点的数据域内装的是什么
头结点的数据域可以为空,也可以存放线性表长度等附加信息,注意此节点不能计入链表长度值
单链表存储表示(带头节点的)
单链表是由表头唯一确定的,因此单链表可以用头指针的名字来命名,若头指针名为L,则把链表称为表L
带头结点的单链表的存储结构
typedef int ElemType;
typedef struct LNode{
ElemType data;
struct Lnode *next;
}LNode, *LinkList;
LinkList L; //定义链表L等效于Lnode *L;
LNode *p; //定义节点指针p
单链表的基本操作
1.链表的初始化
算法步骤:
①生成新结点作为头结点,用头指针L指向头结点
②将头结点的指针域置空
2.判空
3.销毁链表
**算法思想:**从头指针开始,依次释放所有结点
关键代码:
while(L){
p = L;
L = L->next;
delete p;
}
4.清空链表
链表仍存在,但链表中无元素,称为空链表(头指针和头结点均在)
**算法思路:**从首元结点开始依次释放所有的结点,并将头结点指针域设置为空
p = L->next;
while(p){
q = p->next;
delete p;
p = q;
}
L->next = NULL; //头结点指针域置为空
5.求链表表长
**算法思想:**从首元结点开始,依次计数所有结点
int length = 0;
p = L->next;
while(p){
length++;
p = p->next;
}
6.取值
取单链表中第i个元素的内容
//返回链表第loc位置的元素值 查找范围为1到n
int getValue(const LinkList &l, int loc){
LNode *p = l->next;
int j = 1;
while(p && j<loc){
p = p->next;
j++;
}
if(!p || j>loc) return error;//位置不合法
return p->data;
}
7.查找
- 按值查找:根据指定数据返回对应的数据元素
算法步骤:
①从第一个结点起,依次和e相比较
②如果找到一个其值与e相等的数据元素,则返回该元素
③若查遍整个链表都没有找到其值与e相等的元素,则返回0或NULL
//按值查找,返回元素地址 查找范围为1到n(逻辑)
LNode* getElem(const LinkList &l, int e){
LNode *p = l->next;
while(p && p->data!=e){
p = p->next;
}
return p; //若位置不合法返回NULL
}
- 按值查找:返回位置序号
//按值查找,返回元素逻辑位置 查找范围为1到n(逻辑)
int getLoc(const LinkList &l, int e){
LNode *p = l->next;
int j = 1;
while(p && p->data!=e){
p = p->next;
j++;
}
if(!p) return error;//位置不合法
return j;
}
时间复杂度为O(n)
8.插入:在第i个结点前插入新结点
算法思想:找到第i-1个结点p;p->next为原第i位置的结点
s->next=p->next; //s新结点
p->next=s;
//插入 在第loc个结点前插入新元素 插入范围为1到n+1
int insertElem(LinkList &l, int loc, int e){
//1.找到第loc-1个结点 范围为0到n
LNode *p = l;
int j = 0;
while(p && j<loc-1){
p = p->next;
j++;
}
if(!p || j>loc-1) return error;//位置不合法
//2.生成新结点
LNode *s = new LNode;
s->data = e;
//3.插入进链表
s->next = p->next;
p->next = s;
return ok;
}
时间复杂度为O(n)
注意:线性表不需要移动元素,只要修改指针,一般情况下时间复杂度为O(1),但在单链表进行前插或删除操作,要从头查找前驱结点,故时间复杂度为O(n)
9.删除:删除第i个结点
算法思想:找到第i-1个结点p;保存删除结点q的值,返回删除结点的值
q=p->next;
e=q->data;
p->next=q->next;
delete q;
//删除 删除第loc个结点,并返回删除的元素值 删除范围为1到n
int deleteElem(LinkList &l, int loc){
//1.找到第loc-1个结点 范围为0到n-1
LNode *p = l;
int j = 0;
while(p->next && j<loc-1){
//判断条件是p->next 因为loc-1结点范围为0到n-1
p = p->next;
j++;
}
if(!(p->next) || j>loc-1) return error;//位置不合法
//2.删除结点q
LNode *q = p->next;
p->next = q->next;
int e = q->data;
delete q;
return e;
}
时间效率:O(n)
注意:线性表不需要移动元素,只要修改指针,一般情况下时间负责度为O(1),但在单链表进行前插或删除操作,要从头查找前驱结点,故时间复杂度为O(n)
10.单链表的建立
- 头插法 :元素插入在链表头部
算法思想:
①从一个空表开始,重复读入数据;
②生成新结点,将读入数据存放到新结点的数据域中
③依次将各结点插入到链表的前端
//建立单链表 头插法(已知元素个数)
int createList(LinkList &l, int n){
//1.初始化单链表
initList(l);
//2.依次插入到链表的头部
for(int i = n; i > 0; i--){
LNode *p = new LNode;
cin >> p->data;
p->next = l->next;
l->next = p;
}
return ok;
}
时间复杂度O(n)
- 尾插法:元素插入到链表尾部,需要设定尾指针
算法思想:
①从一个空表L开始,将新结点逐个插入到链表的尾部,尾指针r指向链表的尾结点
②初始时,r和L均指向头结点,每读入一个数据元素则申请一个新结点,将新结点插入到尾结点的下一个域,r指向新结点
//建立单链表 尾插发(已知元素个数)
int createList_r(LinkList &l, int n){
//1.初始化单链表
initList(l);
//2.初始化尾指针r
LNode *r = l;
//3.依次插入到链表的尾部
for(int i = 0; i < n; i++){
LNode *p = new LNode;
cin >> p->data;
p->next = r->next; //p->next = NULL 也行
r->next = p;
r = p;
}
return ok;
}
时间复杂度:O(n)
全部代码整合完整cpp
#include<iostream>
using namespace std;
#define ok 1
#define error 0
typedef int elemType;
typedef struct LNode{
elemType data;
struct LNode *next;
}LNode, *LinkList;
int initList(LinkList &l){
l = new LNode;
l->next = NULL;
return ok;
}
int destroyList(LinkList &l){
LNode *p;
while(l){
p = l;
l = l->next;
delete p;
}
return ok;
}
int clearList(LinkList &l){
LNode *p;
while(l->next){
p = l->next;
l->next = p->next;
delete p;
}
return ok;
}
int isEmpty(const LinkList &l){
if(l->next == NULL) return 1;
return 0;
}
int getLength(const LinkList &l){
LNode *p = l->next;
int len = 0;
while(p){
len++;
p = p->next;
}
return len;
}
//返回链表第loc位置的元素值 查找范围为1到n
int getValue(const LinkList &l, int loc){
LNode *p = l->next;
int j = 1;
while(p && j<loc){
p = p->next;
j++;
}
if(!p || j>loc) return error;//位置不合法
return p->data;
}
//按值查找,返回元素地址 查找范围为1到n(逻辑)
LNode* getElem(const LinkList &l, int e){
LNode *p = l->next;
while(p && p->data!=e){
p = p->next;
}
return p; //若位置不合法返回NULL
}
//按值查找,返回元素逻辑位置 查找范围为1到n(逻辑)
int getLoc(const LinkList &l, int e){
LNode *p = l->next;
int j = 1;
while(p && p->data!=e){
p = p->next;
j++;
}
if(!p) return error;//位置不合法
return j;
}
//插入 在第loc个结点前插入新元素 插入范围为1到n+1
int insertElem(LinkList &l, int loc, int e){
//1.找到第loc-1个结点 范围为0到n
LNode *p = l;
int j = 0;
while(p && j<loc-1){
p = p->next;
j++;
}
if(!p || j>loc-1) return error;//位置不合法
//2.生成新结点
LNode *s = new LNode;
s->data = e;
//3.插入进链表
s->next = p->next;
p->next = s;
return ok;
}
//删除 删除第loc个结点,并返回删除的元素值 删除范围为1到n
int deleteElem(LinkList &l, int loc){
//1.找到第loc-1个结点 范围为0到n-1
LNode *p = l;
int j = 0;
while(p->next && j<loc-1){ //判断条件是p->next 因为loc-1结点范围为0到n-1
p = p->next;
j++;
}
if(!(p->next) || j>loc-1) return error;//位置不合法
//2.删除结点q
LNode *q = p->next;
p->next = q->next;
int e = q->data;
delete q;
return e;
}
//建立单链表 头插法(已知元素个数)
int createList(LinkList &l, int n){
//1.初始化单链表
initList(l);
//2.依次插入到链表的头部
for(int i = n; i > 0; i--){
LNode *p = new LNode;
cin >> p->data;
p->next = l->next;
l->next = p;
}
return ok;
}
//建立单链表 尾插发(已知元素个数)
int createList_r(LinkList &l, int n){
//1.初始化单链表
initList(l);
//2.初始化尾指针r
LNode *r = l;
//3.依次插入到链表的尾部
for(int i = 0; i < n; i++){
LNode *p = new LNode;
cin >> p->data;
p->next = r->next; //p->next = NULL 也行
r->next = p;
r = p;
}
return ok;
}
//建立单链表 头插法(未知元素个数)
int createList(LinkList &l){
//1.初始化单链表
initList(l);
//2.依次插入到链表的头部
int e, j = 0;
while(1){
cin >> e;
LNode *p = new LNode;
p->data = e;
p->next = l->next;
l->next = p;
if(cin.get() == '\n')//要放在最后面否则会少一个数据
break;
}
return ok;
}
//建立单链表 尾插发(未知元素个数)
int createList_r(LinkList &l){
//1.初始化单链表
initList(l);
//2.初始化尾指针r
LNode *r = l;
//3.依次插入到链表的尾部
int e;
while(1){
cin >> e;
LNode *p = new LNode;
p->data = e;
p->next = r->next;
r->next = p;
r = p;
if(cin.get() == '\n')
break;
}
return ok;
}
//输出链表元素
int printList(const LinkList &l){
if(isEmpty(l))
cout << "链表为空" << endl;
LNode *p = l->next;
int j = 0;
while(p){
j++;
cout << p->data << " ";
p = p->next;
}
cout << endl;
return ok;
}
int main(){
LinkList l1, l2;
createList_r(l1, 4);
insertElem(l1, 2, -1);
printList(l1);
cout << "第2个位置元素的值=" << getValue(l1, 2) << endl;
cout << "删除第3个元素,第3个元素的值=" << deleteElem(l1, 3) << endl;
printList(l1);
int loc = getLoc(l1, 7);
cout << "值为7的元素的逻辑位置是" << loc;
LNode *t = getElem(l1, 7);
cout << t << " " << t->data << endl;
createList_r(l2);
printList(l2);
cout << getLength(l2) <<endl;
return 0;
}