一、线性表的定义和基本操作
(一)定义:
线性表是具有相同数据类似的n个元素组成的有限序列,n为表长,当n=0时为空表,用L命名线性表,则表示方式:L=(a1,a2,…,an)。【脚标从1开始】
相同数据元素:每个数据元素占有的空间一样大,有利于计算机快速找出具体的元素
有限:有限个。
序列:有次序。
除了第一个元素,其他元素均有唯一一个前驱,除了最后一个元素,其他元素均有唯一一个后继。
(二)基本操作:
1.表的初始化和销毁:
tip:&引用类型的作用:如果参数的修改结果需要带回来,则需要&。【C++内容,C不支持】
1.1.初始化【从无到有】:
InitList(&L):构造一个空的线性表L,分配内存空间;
1.2.销毁【从有到无】:
DestroyList(&L):销毁线性表,并释放表L所占有的内存空间;
2.插入和删除:
2.1.插入:
ListInsert(&L,i,e):在表L的第i个位置插入元素e;
2.2.删除:
ListDelete(&L,i,&e):删除表L的第i个位置的元素,并用e返回删除元素的值;
3.查找:
3.1.按值查找:
LocateElem(L,e):在表L中查找具有给定值的元素;
3.2.按位查找:
GetElem(L,i):获取表L中第i位置的元素值;
4.其他操作:
4.1.求表长:
Length(L):返回表L的长度;
4.2.输出:
PrintList(L):按前后顺序输出表L的所有元素;
4.3.判空:
Empty(L):为空返回true,否则返回false;
二、线性表的实现
(一)顺序存储(顺序表):
1.定义:
用顺序存储的方式实现线性表;把逻辑上相邻的元素存储在物理上也相邻的存储单元中;
如第一个元素的位置是K,则第二个元素的位置是K+这个元素的大小【sizeof(ElemType)】,例如:
typedef struct{
int num;
int people;
}Customer;
一个int占4个字节(byte),则此结构占有8个字节,sizeof(Customer) = 8B;
2.实现方式:
2.1.静态存储:
2.1.1.基本格式:
#define MaxSize 10;//表的最大长度
typedef struct{
ElemType data(MaxSize);//用静态数组存储,ElemType:代表所有数据类型,使用时用int等类型替换
int length;//当前长度
}Sqlist;//类型定义,此结构的别名
2.1.2.C语言实现:
//定义顺序表的静态存储结构
#define MaxSize 10 //定义静态存储最大内存为10
typedef struct{
int data[MaxSize];//用静态数组存储元素
int length; //当前表长
}SqList;
//初始化表
void InitList(SqList &L){
for(int i = 0;i < MaxSize;i ++){
L.data[i] = 0;//每个数组赋默认值为0,作用:清除之前储存的脏数据
L.length = 0;//当前表长为0
}
}
int main(){
SqList L;//定义结构,类似于int L
InitList(L);
return 0;
}
如果数组存满了怎么办:没办法,呵呵🙂!
2.2.动态分配:
2.2.1.基本格式:
#define InitSize 10
typedef struct{
ElemType* data;//动态分配数组的指针
int MaxSize;//表的最大容量
int length;//当前表长
}SeqList;
注:
动态申请和释放内存空间:malloc申请,free释放
malloc申请一整片的内存空间:L.data = (ElemType **)malloc(sizeof(ElemType) * InitSize);
其中:
(ElemType **)为强制转换为自己所需要的数据元素类型
(sizeof(ElemType):所使用的数据类型大小 InitSize:初始长度
sizeof(ElemType) * InitSize:数据类型大小 x 初始长度 = 总大小
2.2.2.C语言实现:
#include <stdio.h>
#include <stdlib.h>//调用malloc的库函数 ,或#include <malloc.h>
//顺序表的动态分配
#define InitSize 10//默认最大长度
typedef struct{
int* data;//动态分配数组的指针
int MaxSize;//最大容量
int length;//当前长度
}SeqList;
//初始化顺序表
void InitList(SeqList &L){
L.data = (int*)malloc(sizeof(int)*InitSize);//用malloc申请一整块内存
L.length = 0;
L.MaxSize = InitSize;//最大容量为最大长度
}
//增加动态数组的长度
void IncreaseSize(SeqList &L,int len){
int* p = L.data;//将L.data数据暂存在p中
L.data = (int*)malloc(sizeof(int)*(L.MaxSize+len)); //申请+len大小的地址
for(int i = 0;i < L.length;i ++){
L.data[i] = p[i];//数据转移到新地址
}
L.MaxSize = L.MaxSize+len;
free(p);//释放p指针
}
int main(){
SeqList L;
InitList(L);
IncreaseSize(L,10);
return 0;
}
3.顺序表的特点:
随机访问:可以在O(1)时间内找到第i个元素,因为存放位置连续存放
存储密度高:每个结点只存储数据元素,不需要存储指针等
拓展不方便:即使采用动态分配,拓展长度的时间复杂度也比较高
插入,删除不方便
4.顺序表的基本操作:
4.1.插入:
ListInsert(&L,i,e);
4.1.1.代码实现:
#include <stdio.h>
//基于静态分配的线性表定义
#define MaxSize 10
typedef struct{
int data[MaxSize];
int length;
}SqList;
//初始化线性表
void InitList(SqList &L){
for(int i = 0;i < MaxSize;i ++){
L.data[i] = 0;
L.length = 0;
}
}
//在第i个位置插入元素e
bool ListInsert(SqList &L,int i,int e){//bool用于返回异常情况
//条件判断
if(i<1 || i>L.length+1){//判断i的有效范围
return false;
}
if(L.length > MaxSize || L.length == MaxSize){//判断存储空间是否已满
return false;
}
//将第i个元素及之后的后移
for(int j = L.length;j >= i;j --){
L.data[j] = L.data[j - 1];
}
//在位置i插入元素e
L.data[i-1] = e;//数组开始下标为0
L.length ++;
return true;
}
int main(){
SqList L;
InitList(L);
ListInsert(L,3,4);
}
4.1.2.时间复杂度分析:
关注最深层循环的语句:
for(int j = L.length;j >= i;j --){
L.data[j] = L.data[j - 1];
}
问题规模:n = L.length(表长)
最好情况:新元素插在表尾,需要移动0个元素,时间复杂度=O(1);
最坏情况:新元素插在表头,需要将原有的n个元素全部移动,当i = 1时,循环n次,时间复杂度=O(n);
平均情况:假设新元素插入到任何位置的概率都相同,即i = 1,2,3,…,length+1的概率为P=1/(n+1)
当i=1时,循环n次;i=2时,循环-1次;i=3时,循环n-2次,…,i=n时,循环0次
则平均循环次数为:nP+(n-1)P+(n-2)P+…+1P=n/2,则时间复杂度=O(n);
4.2.删除:
ListDelete(&L,i,e);
4.2.1.代码实现:
//用静态数组表示顺序表
#define MaxSize 10
typedef struct{
int data[MaxSize];
int length;
}SqList;
//初始化顺序表
void InitList(SqList &L){
for(int i = 0;i < MaxSize;i ++){
L.data[i] = 0;//给数据元素赋默认值0
L.length = 0;//初始长度为0
}
}
//在第i个位置插入元素e
bool ListInsert(SqList &L,int i,int e){
//是否满足插入的条件判断 ,不满足返回false
if(i < 1 || i > L.length+1){
return false;
}
if(L.length > MaxSize || L.length == MaxSize){
return false;
}
//i及其后的原有元素依次后移一位
for(int j = L.length;j >= i;j --){
L.data[j] = L.data[j-1];
}
//元素e插入,返回true
L.data[i-1] = e;
L.length++;
return true;
}
//删除表中第i个位置的元素,并且用e返回此元素值
bool ListDelete(SqList &L,int i,int &e){//&e便于返回e的值
//条件判断
if(i < 1 || i > L.length){
return false;
}
//首先e接收第i个位置的值
e = L.data[i-1];
//然后i之后的元素依次前移
for(int j = i;i < L.length;j ++){
L.data[j-1] = L.data[j];
}
L.length--;
return true;
}
int main(){
SqList L;
//初始化顺序表
InitList(L);
//插入元素
// 删除L表第3个位置的元素,并由e返回其值
int e = -1;
ListDelete(L,2,e);
if(ListDelete(L,2,e)){
printf("已删除第2个元素,删除值为:%d\n",e);
}else{
printf("删除失败!");
}
return 0;
}
4.2.2.时间复杂度分析:
for(int j = i;i < L.length;j ++){
L.data[j-1] = L.data[j];
}
最好情况:删除表尾元素,需要移动0个元素,时间复杂度=O(1);
最坏情况:删除表头元素,需要将原有的n-1个元素全部移动,当i = 1时,循环n-1次,时间复杂度=O(n);
平均情况:假设删除任何一个元素的概率都相同,即i = 1,2,3,…,length+1的概率为P=1/n
当i=1时,循环n-1次;i=2时,循环-2次;i=3时,循环n-3次,…,i=n时,循环0次
则平均循环次数为:(n-1)P+(n-2)P+…+1P=(n-)/2,则时间复杂度=O(n);
5.按位查找:
GetElem(L,i):获取表L中第i个位置的元素值
5.1.代码实现:
I.若顺序表使用静态分配:
#define MaxSize 10
typedef struct{
ElemType data[MaxSize];
int length;
}SqList;
则按位查找为:
ElemType GetElem(SqLit L,int i){
return L.data[i-1];
}
II.若线性表使用动态分配:
#define InitSize 10
typedef struct{
ElemType* data;
int MaxSize;
int length;
}SeqList;
则按位查找为:
ElemType GetElem(SeqList L.int i){
return L.data[i-1];//指针可以认为数组,和数组一样的操作
}
5.2.时间复杂度分析:
因为没有循环等,只是单纯的返回,所以事件复杂度为O(1)
6.按值查找:
LocatElem(L,e):在表L中查找具有给定值的元素
6.1.代码实现:
//动态分配
#define InitSize 10
typedef struct{
ElemType* data;
int MaxSize;
int length;
}SeqList;
//按值查找
int LocateElem(SeqList L,ElemType e){
for(int i = 0;i < L.length;i++){
if(L.data[i] == e){
return i+1;//数组下标为i的元素等于e,其位序为i+1
}
}
return 0;//查询失败
}
6.2.时间复杂度分析:
最好情况:数组第一个元素就匹配,时间复杂度=O(1)
最坏情况:数组最后一个才匹配,时间复杂度=O(n)
平均情况:假设目标元素出现在任何位置的概率都相同,都是1/n
目标元素在第1位,循环1次;在低2位,循环2次;…;在第n位,循环n次
则,平均循环次数:11/n+21/n+…+n*1/n = (n+1)/2,则平均时间复杂度=O(n)
(二)链式存储(链表):
1.单链表:
1.1.定义:
每个结点除了存放数据元素外,还需要存储指向下一个结点的指针
特点:
不要求大片的连续空间,方便改变容量
不可随机存取,需要耗费一定的空间存放指针
复习,顺序表的特点:
随机访问:可以在O(1)时间内找到第i个元素,因为存放位置连续存放
存储密度高:每个结点只存储数据元素,不需要存储指针等
拓展不方便:即使采用动态分配,拓展长度的时间复杂度也比较高
插入,删除不方便
1.2.代码实现:
typedef struct LNode{//定义结点类型
ElemType data;//数据域
struct LNode* next;//指针域
}LNode,*LinkList;//用LinkList表示这是struct LNode的指针,可以是LNode* 也可以是LinkList,相等
//当代码强调这是一个单链表时,使用LinkList;强调这是一个指针时,使用LNod*
//动态申请空间,p指针指向此空间
LinkList p = (struct LNode*)malloc(sizeof(struct LNode));
1.2.1.带头结点:
typedef struct LNode{
ElemType data;
struct LNode* next;
}LNode,*LinkList;
//初始化一个空的,带头结点的单链表
bool InitList(LinkList &L){
L = (LNode*)malloc(sizeof(LNode));//为头结点分配空间
if(L == NULL){//判断头结点内存是否已分配
return false;
}
L->next = NULL;//头结点之后暂时没有结点,避免脏数据
return true;
}
void test(){
LinkList L;
InitList(L);
}
//判断单链表是否为空
bool Empty(LinkList L){
return (L->next == NULL);
}
1.2.2.不带头结点:
typedef struct LNode{
ElemType data;
struct LNode* next;
}LNode,*LinkList;
//初始化一个空的,不带头结点的单链表
bool InitList(LinkList &L){
L = NULL;//空表,暂时没有任何结点,防止脏数据
return true;
}
void test(){
LinkList L;//声明单链表
InitList(L);
}
//判断单链表是否为空
bool Empty(LinkList l){
return (L == NULL);
}
带不带头结点的特点:
不带头节点,写代码麻烦,对第一个数据结点和后续结点的处理需要用不同的代码逻辑,对空表和非空表的处理需要不同的代码逻辑
带头结点,写代码方便
1.3.基本操作:
1.3.1.插入:
1.3.1.1.按位序插入:
I.带头结点:
ListInsert(&L,i,e):需要找到第i-1个结点,将新结点插入
比如:在第2个位置插入元素e,即是找到第1个结点,在此结点的next域申请空间,并插入
#include <stdio.h>
#include <stdlib.h>//nalloc()函数的调用库
//定义单链表
typedef struct LNode{
int data;
struct LNode* next;
}LNode,*LinkList;
//初始化单链表(带头结点)
bool InitList(LinkList &L){
L = (LNode*)malloc(sizeof(LNode));
if(L == NULL){//头结点判空条件
return false;
}
L->next = NULL;
return true;
}
//在第i个位置插入元素e
bool ListInsert(LinkList &L,int i,int e){
//是否满足插入的条件判断
if(i < 1){
return false;
}
//具体插入实现
LNode* p;
p = L;//将L的头指针给p
if(p == NULL){//若头指针为NULL则返回false
return false;
}
int j = 0;
while(p != NULL && j < i-1){
p = p->next;//所有元素后移
j++;
}
LNode* s = (LNode*)malloc(sizeof(LNode)); //为插入位置开辟空间
s->data = e;//元素e放入s的数据域
s->next = p->next;//s的指针域指向p+1的数据域
p->next = s;//p的指针域指向s的数据域
return true;
}
int main(){
LinkList L;
InitList(L);
ListInsert(L,2,3);
}
II.不带头结点:
#include <stdio.h>
#include <stdlib.h>
//定义一个单链表
typedef struct LNode{
int data;
struct LNode* next;
}LNode,*LinkList;
//初始化单链表(不带头结点)
bool InitList(LinkList &L){
L = NULL;
return true;
}
//插入元素
bool ListInsert(LinkList &L,int i,int e){
//第1个位置插入元素e
if(i == 1){
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = L;//s的指针域指向原有的L链表
L = s;//将s赋给L
return true;
}
//其他位置插入元素e
LNode* p;
int j = 1;//注意此时j为1
p = L;
while(p != NULL && j < i-1){
p = p->next;
j++;
}
if(p == NULL){
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
1.3.2.指定结点的后插:
后插:给定一个结点,在此结点之后插入元素e
#include <stdio.h>
#include <stdlib.h>
//定义单链表
typedef struct LNode{
int data;
struct LNode* next;
}LNode,*LinkList;
//初始化单链表(带头结点)
bool InitList(LinkList &L){
L = (LNode*)malloc(sizeof(LNode));
L->next = NULL;
return true;
if(L == NULL){
return false;
}
}
//在第i个位序插入元素e
bool ListInsert(LinkList &L,int i,int e){
//条件判断
if(i < 1){
return false;
}
//将L的头结点赋给p
LNode* p;
p = L;
//从头结点开始,依次移动指针指到位序i
if(p == NULL){
return false;
}
int j = 0;
while(p != NULL && j < i-1){
p = p->next;
j++;
}
//给s指针开票空间,存放e,并且插入到p中
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
//后插操作,在元素p的后面插入元素e
bool InsertNextLNode(LNode* p,int e){
if(p == NULL){
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
if(s == NULL){//空间分配失败
return false;
}
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
1.3.3.指定结点的前插:
在指定结点前面插入元素e
#include <stdio.h>
#include <stdlib.h>
//定义单链表
typedef struct LNode{
int data;
struct LNode* next;
}LNode,*LinkList;
//初始化单链表(带头结点)
bool InitList(LinkList &L){
L = (LNode*)malloc(sizeof(LNode));
L->next = NULL;
return true;
if(L == NULL){
return false;
}
}
//在第i个位序插入元素e
bool ListInsert(LinkList &L,int i,int e){
//条件判断
if(i < 1){
return false;
}
//将L的头结点赋给p
LNode* p;
p = L;
//从头结点开始,依次移动指针指到位序i
if(p == NULL){
return false;
}
int j = 0;
while(p != NULL && j < i-1){
p = p->next;
j++;
}
//给s指针开票空间,存放e,并且插入到p中
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
//后插操作,在元素p的后面插入元素e
bool InsertNextLNode(LNode* p,int e){
if(p == NULL){
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
if(s == NULL){//空间分配失败
return false;
}
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
//前插操作,在元素p的前面插入元素e
bool InsertPriorLNode(LNode* p,int e){
if(p == NULL){
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
if(s == NULL){
return NULL;
}
s->next = p->next;//S的next域指向p的下一结点
p->next = s;//p的next域指向s的数据域
s->data = p->data;//将p的数据赋到s的数据域里
p->data = e;//将元素e赋值到p的数据域里
return true;
}
1.3.2.删除:
1.3.2.1.按位序删除:
I带头结点:
ListDelete(&L,i,&e):删除表L中第i个位置的元素,并用e返回元素的值,需要找到第i-1个结点,将其指向第i+1个结点,并释放第i个结点。
#include <stdio.h>
#include <stdlib.h>
//定义单链表
typedef struct LNode{
int data;
struct LNode* next;
}LNode,*LinkList;
//初始化单链表(带头结点)
bool InitList(LinkList &L){
L = (LNode*)malloc(sizeof(LNode));
L->next = NULL;
return true;
if(L == NULL){
return false;
}
}
//在第i个位序插入元素e
bool ListInsert(LinkList &L,int i,int e){
//条件判断
if(i < 1){
return false;
}
//将L的头结点赋给p
LNode* p;
p = L;
//从头结点开始,依次移动指针指到位序i
if(p == NULL){
return false;
}
int j = 0;
while(p != NULL && j < i-1){
p = p->next;
j++;
}
//给s指针开票空间,存放e,并且插入到p中
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
//后插操作,在元素p的后面插入元素e
bool InsertNextLNode(LNode* p,int e){
if(p == NULL){
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
if(s == NULL){//空间分配失败
return false;
}
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
//前插操作,在元素p的前面插入元素e
bool InsertPriorLNode(LNode* p,int e){
if(p == NULL){
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
if(s == NULL){
return NULL;
}
s->next = p->next;//S的next域指向p的下一结点
p->next = s;//p的next域指向s的数据域
s->data = p->data;//将p的数据赋到s的数据域里
p->data = e;//将元素e赋值到p的数据域里
return true;
}
//按位序删除
bool ListDelete(LinkList &L,int i,int &e){//e需要带回,则需要&
if(i < 1){
return false;
}
LNode* p;
p = L;
int j = 0;
while(p != NULL && j < i -1){
p = p->next;//循环完成后,p指针指向i-1个结点的位置,即若删除第4个元素 ,则目前p指向第3个
j++;
}
if(p == NULL){
return false;
}
if(p->next == NULL){
return false;
}
LNode* q = p->next;//q指针指向p
e = q->data;//q的数据赋给e
p->next = q->next;//p直接指向q的下一个结点,即孤立q结点
free(q);
return true;
}
1.3.2.2.指定结点的删除:
bool DeleteNode(LNode *p):删除结点p,需要知道p的前一个结点
bool DeleteNode(LNode* p){
if(p == NULL){
return false;
}
LNode* q = p->next;//循环后,p指向i-1个位置,则q结点指向p
p->data = q->data;//数据交换
p->next = q->next;//i的前一个结点(i-1),直接指向q的下一个结点(i+1),即绕过了q
free(q);
return true;
}
1.3.3.查找:
1.3.3.1.按位查找:
GetElem(L,i):获取表L中第i个元素的值
bool GetElem(LinkList L,int i){
if(i < 0){
return false;
}
LNode* p;
p = L;
int j = 0;
while(p != NULL && j < i){
p = p->next;
j ++;
}
return p;
}
1.3.3.2.按值查找:
LocateElem(L,E):在表L中查找具有给定关键字值的元素
bool LocateElem(LinkList L,int e){
LNode* p =L->next;
while(p != NULL && p->data != e){//从第一个结点开始查找
p = p->next;
}
return p;
}
1.3.4.求表长:
int Length(LinkList L){
int len = 0;//统计表长
LNode* p = L;
while(p->next != NULL){
p = p->next;
len++;
}
return len;
}
1.3.5.单链表的建立:
如果给你很多数据元素,要求把他们存到一个单链表中,咋neng呢?
首先:初始化一个单链表
其次:每次去一个还俗,插入到表尾(尾插)/表头(头插)
1.3.5.1.尾插法:
bool TailInsert(LinkList L){
L = (LinkList)malloc(sizeof(LNode));//创建头结点
L->next = NULL;
int x;
scanf("%d\n",&x);
LNode* s,*r = L;//s为插入的元素,r为表尾元素
while(x != 9999){//当输入9999表示插入结束
s = (LNode*)malloc(sizeof(LNode));
s->data = x;//插入元素放入s的数据域中
r->next = s;//表尾的next域指向插入的元素s
r = s;//保证r一直指向表L的表尾
scanf("%d\n",&x);
}
r->next = NULL;
return L;
}
1.3.5.2.头插法(逆置,输入4 5 6,插入为6 5 4):
bool HeadInsert(LinkList L){
L = (LinkList)malloc(sizeof(LNode));//创建头结点
L->next = NULL;//避免脏数据
int x;
scanf("%d\n",&x);
LNode* s;
while(x != 9999){//当输入9999表示插入结束
s = (LNode*)malloc(sizeof(LNode));
s->data = x;
s->next = L->next;
L->next = s;
scanf("%d\n",&x);
}
return L;
}
1.4.代码合集:
#include <stdio.h>
#include <stdlib.h>
//定义单链表
typedef struct LNode{
int data;
struct LNode* next;
}LNode,*LinkList;
//初始化单链表(带头结点)
bool InitList(LinkList &L){
L = (LNode*)malloc(sizeof(LNode));
L->next = NULL;
return true;
if(L == NULL){
return false;
}
}
//在第i个位序插入元素e
bool ListInsert(LinkList &L,int i,int e){
//条件判断
if(i < 1){
return false;
}
//将L的头结点赋给p
LNode* p;
p = L;
//从头结点开始,依次移动指针指到位序i
if(p == NULL){
return false;
}
int j = 0;
while(p != NULL && j < i-1){
p = p->next;
j++;
}
//给s指针开票空间,存放e,并且插入到p中
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
//后插操作,在元素p的后面插入元素e
bool InsertNextLNode(LNode* p,int e){
if(p == NULL){
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
if(s == NULL){//空间分配失败
return false;
}
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
//前插操作,在元素p的前面插入元素e
bool InsertPriorLNode(LNode* p,int e){
if(p == NULL){
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
if(s == NULL){
return NULL;
}
s->next = p->next;//S的next域指向p的下一结点
p->next = s;//p的next域指向s的数据域
s->data = p->data;//将p的数据赋到s的数据域里
p->data = e;//将元素e赋值到p的数据域里
return true;
}
//按位查找,返回第i个元素
bool GetElem(LinkList L,int i){
if(i < 0){
return false;
}
LNode* p;
p = L;
int j = 0;
while(p != NULL && j < i){
p = p->next;
j ++;
}
return p;
}
//按值查找,找到数据域==e的结点
bool LocateElem(LinkList L,int e){
LNode* p =L->next;
while(p != NULL && p->data != e){//从第一个结点开始查找
p = p->next;
}
return p;
}
//求表长:
int Length(LinkList L){
int len = 0;//统计表长
LNode* p = L;
while(p->next != NULL){
p = p->next;
len++;
}
return len;
}
//尾插法建立单链表
bool TailInsert(LinkList L){
L = (LinkList)malloc(sizeof(LNode));//创建头结点
L->next = NULL;
int x;
scanf("%d\n",&x);
LNode* s,*r = L;//s为插入的元素,r为表尾元素
while(x != 9999){//当输入9999表示插入结束
s = (LNode*)malloc(sizeof(LNode));
s->data = x;//插入元素放入s的数据域中
r->next = s;//表尾的next域指向插入的元素s
r = s;//保证r一直指向表L的表尾
scanf("%d\n",&x);
}
r->next = NULL;
return L;
}
//尾插法建立单链表
bool HeadInsert(LinkList L){
L = (LinkList)malloc(sizeof(LNode));//创建头结点
L->next = NULL;//避免脏数据
int x;
scanf("%d\n",&x);
LNode* s;
while(x != 9999){//当输入9999表示插入结束
s = (LNode*)malloc(sizeof(LNode));
s->data = x;
s->next = L->next;
L->next = s;
scanf("%d\n",&x);
}
return L;
}
int main(){
return 0;
}
2.双链表:
typedef struct DNode{
ElemType data;
struct DNode *prior,*next;
}DNode,*DLinkList;
2.1.初始化:
bool InitDLinkList(DLinkList &L){
L = (DNode*)malloc(sizeof(DNode));
if(L == NULL){
return false;
}
L->prior = NULL;
L->next = NULL;
return true;
}
2.2.插入:
bool InsertNextDNode(DNode* p,DNode* s){
if(p == NULL || s == NULL){
return false;
}
s->next = p->next;
if(p->next != NULL){//避免空指针异常
p->next->prior = s;
}
s->prior = p;
p->next = s;
return true;
}
2.3.删除:
bool DNodeDelete(DNode* p){
if(p ==NULL){
return false;
}
DNode* q = p->next;
if(q == NULL){
return false;
}
p->next = q->next;
if(q->next != NULL){
q->next->prior = p;
}
free(q);
return true;
}
//销毁
void DestoryList(DLinkList &L){
while(L->next != NULL){
DNodeDelete(L);
}
free(L);
L = NULL;
}
2.4.遍历:
//自前向后
while(p != NULL){
p = p->next;
}
//自后向前
while(p != NULL){
p = p->prior;
}
2.5.代码合集:
#include <stdio.h>
#include <stdlib.h>
//创建双链表
typedef struct DNode{
int data;
struct DNode* prior, *next;//声明头结点和尾结点
}DNode,*DLinkList;
//初始化双链表
bool InitDLinkList(DLinkList &L){
L = (DNode*)malloc(sizeof(DNode));
if(L == NULL){
return false;
}
L->prior = NULL;
L->next = NULL;
return true;
}
//双链表的插入,p结点之后插入s
bool InsertNextDNode(DNode* p,DNode* s){
if(p == NULL || s == NULL){
return false;
}
s->next = p->next;
if(p->next != NULL){//避免空指针异常
p->next->prior = s;
}
s->prior = p;
p->next = s;
return true;
}
//删除
bool DNodeDelete(DNode* p){
if(p ==NULL){
return false;
}
DNode* q = p->next;
if(q == NULL){
return false;
}
p->next = q->next;
if(q->next != NULL){
q->next->prior = p;
}
free(q);
return true;
}
//销毁
void DestoryList(DLinkList &L){
while(L->next != NULL){
DNodeDelete(L);
}
free(L);
L = NULL;
}
3.循环链表:
3.1.循环单链表:
单链表的最后结点指针指向NULL,循环单链表最后结点指针指向头结点
#include <stdio.h>
#include <stdlib.h>
//定义循环单链表
typedef struct LNode{
int data;
struct LNode* next;
}LNode,*LinkList;
//初始化循环单链表
bool InitList(LinkList &L){
L = (LNode*)malloc(sizeof(LNode));
if(L == NULL){
return false;
}
L->next = L;//头指针的next域指向头结点
return true;
}
//判表空
bool Empty(LinkList L){
if(L->next == L){
return true;
}else{
return false;
}
}
//判断结点p是否为循环单链表L的表尾结点
bool isTail(LinkList L,LNode* p){
if(p->next == L){
return true;
}else{
return false;
}
}
3.2.循环双链表:
#include <stdio.h>
#include <stdlib.h>
//定义一个循环双链表
typedef struct DNode{
int data;
struct DNode* prior;
struct DNode* next;
}DNode,*DLinkList;
//初始化循环双链表
bool InitList(DLinkList &L){
L = (DNode*)malloc(sizeof(DNode));
if(L == NULL){
return false;
}
L->next = L;
L->prior = L;
return true;
}
//判表空
bool Empty(DLinkList L){
if(L->next == L){
return true;
}else{
return false;
}
}
//判断结点p是否为表尾结点
bool isTail(DLinkList L,DNode* p){
if(p->next == L){
return true;
}else{
return false;
}
}
4.静态链表:
4.1.概念:
不同于单链表,单链表各个结点的内存是零散的分布在内存空间中,通过指针连接;而静态链表需要分配一整片连续的内存空间,各个结点集中安置,每个结点包含数据元素和下一个结点的数组下标,下标为0的结点充当头结点,每个数据元素4B,每个游标4B
4.2.定义:
#include <stdio.h>
#include <stdlib.h>
//定义静态链表
#define MaxSize 10
typedef struct Node{
ElemType data;//储存的数据元素
int next;//下一个元素的数组下标
}SLinkList[MaxSize]; //重命名为数组,后面写为:SLinkList a,a即为数组
5.顺序表和链表对比:
5.1.逻辑结构:
都是线性表,线性结构
5.2.物理结构/存储结构:
顺序表支持随机存取,存储密度高,但需要大片连续的空间,不方便改变容量
链表只需要离散的小空间,便于分配,改变容量方便,但不可随机存取,存储密度低
5.3.数据运算/基本操作:
创建:顺序表虽容量可以用malloc改变,但需要移动大量元素,时间代价大
销毁:malloc和free成对出现,链表都是用malloc创建的,而顺序表的静态数组自动回收
增删:顺序表需要移动大量后续元素,链表只需要修改指针
改查:顺序表可以可以随机修改
5.4.如何抉择:
表长难以估计,需要经常增删——链表
表长可估计,查询搜索多——顺序表