文章目录
线性表的定义和基本操作
定义
基本操作
//线性表的基本操作
//初始化
InitList(&L)
//销毁操作
DestroyList(&L)
//插入操作
ListInsert(&L,i,e)
//删除操作
ListDelete(&L,i,&e)
//按值查找操作
LocateElem(L,e)
//按位查找操作
GetElem(L,i)
//求表长
Length(L)
//输出操作
PrintList(L)
//判空操作
IsEmptyList(L)
- 什么时候要传入参数的印用 “&” ——对参数的修改结果需要 “带回来” 的时候
- 这里的印用 “&”是C++才支持的写法
//对参数的修改“没带回来”的情况
#include<stdio.h>
void test(int x){
x = 1024;
printf("test函数内部 x=%d\n",x);
}
int main(){
int x = 1;
printf("调用test函数前 x=%d\n",x);
test(x);
printf("调用test函数后 x=%d\n",x);
}
//输出结果:
//调用test函数前 x=1
//test函数内部 x=1024
//调用test函数后 x=1
- 这里由于函数调用栈的存在,在test函数内部对x的修改可以看作main函数中的x的一个复制品,它的值的改变并不会影响到main函数里面的x的值
//对参数的修改“带回来了”的情况
#include<stdio.h>
void test(int &x){ //一个小小的印用&,结果却各不相同
x = 1024;
printf("test函数内部 x=%d\n",x);
}
int main(){
int x = 1;
printf("调用test函数前 x=%d\n",x);
test(x);
printf("调用test函数后 x=%d\n",x);
}
//输出结果:
//调用test函数前 x=1
//test函数内部 x=1024
//调用test函数后 x=1024
- 这里由于test函数的形参x前加入了引用符号&,即使有函数调用栈的存在,但由于是引用,在test函数内部对x的修改确实是修改了x的真实的值,main函数中的x的值也相应地被修改了
- 画黄色的部分就是要将对形参的修改结果带回来的情况,所以都加上了引用符号&
顺序表的定义
- 用【顺序存储】的方式实现的线性表
顺序表的定义
顺序表的实现—静态分配
- 既然存在脏数据那么 length = 0 这一步就不应该被省略
顺序表的实现—动态分配
- malloc函数所实现的原理是会申请一整片连续的存储空间,这个函数执行之后会返回指向这片连续的内存空间的开始地址的指针,可以实现动态申请内存空间
- malloc函数使用的时候记得把返回的指针强制转化为你所定义的数据元素类型指针
- free函数可以实现动态释放内存空间
顺序表的特点
顺序表的插入和删除
顺序表的基本操作—插入
- 注意保证代码的健壮性,这里还需要对传入的参数进行判断,比如插入的位置必须在1~length+1之间;又或者顺序表存满了的话应该不允许插入行为的发生
插入操作的时间复杂度
顺序表的基本操作—删除
删除操作的时间复杂度
顺序表的查找
顺序表的按位查找
- 用某一类型的指针加上数组下标的这种方式来访问数据的话,那么系统在背后为你取数据的时候,每一次取几个字节其实和你这个指针所指向的类型有关
- 这就解释了为什么我们用malloc函数申请一片连续的内存空间之后要把函数返回的地址强制转化为和你的数据类型相对应的同类型的指针
- 因为虽然指针指向的都是同一个地址,但是如果指针所指的数据类型你给它定义错了,那么在访问里面的数据元素的时候也会出现问题
按位查找的时间复杂度
顺序表的按值查找
结构类型的比较
按值查找的时间复杂度
顺序表的基本操作代码实现
#include<stdio.h>
#include<stdlib.h>
//顺序表的定义(静态分配)
#define MaxSize 10 //最大长度
typedef struct{
int data[MaxSize]; //用静态数组存放数据元素
int length; //当前长度
}SqList;
//顺序表的定义(动态分配)
typedef struct{
int *data; //动态分配数组的指针
int length; //当前长度
int Maxsize; //最大容量
}SeqList;
//静态顺序表初始化
#define InitSize 10 //最大长度
void InitList(SqList &L){
for(int i = 0; i<L.length;i++){
L.data[i] = 0;
}
L.length = 0;
}
//静态顺序表的插入操作
bool InsertList(SqList &L,int i,int e){
if(i<1 || i>L.length+1){
return false;
}
if(L.length>=MaxSize){
return false;
}
for(int j = L.length;j>=i;j--){ //将第i个元素及其以后的所有元素后移
L.data[j] = L.data[j-1];
}
L.data[i-1] = e; //在位置i处存入e
L.length++; //顺序表长度+1
return true;
}
//静态顺序表的删除操作
bool ListDelete(SqList &L,int i,int &e){
if(i<1 || i>L.length+1){
return false;
}
e = L.data[i-1];
for(int j = i;j<L.length;j++){
L.data[j-1] = L.data[j];
}
L.length--;
return true;
}
//静态顺序表的按位查找
int GetElem(SqList L,int i){
return L.data[i-1];
}
//静态顺序表的按值查找
int LocateElem(SeqList L,int e){
for(int i = 0;i<L.length;i++){
if(e == L.data[i]){
return i+1;
}
}
return 0;
}
//动态顺序表初始化
void InitList(SeqList &L){
//用malloc函数申请一片连续的存储空间
L.data = (int*)malloc(InitSize * sizeof(int));
L.length = 0;
L.MaxSize = InitSize;
}
//增加动态数组的长度
void IncreaseSize(SeqList &L,int len){
int *p = L.data;
L.data = (int *)malloc((L.MaxSize+len)*sizeof(int));
for(int i = 0;i<p.length;i++){
L.data[i] = p.data[i]
}
L.MaxSize += len; //顺序表最大长度增加len
free(p); //释放原来的内存空间
}
int main(){
SqList L; //声明一个顺序表
InitList(L); //初始化
if(InsertList(L,3,3)){
printf("插入成功")
}else{
printf("插入失败")
}
int deleteElem = -1;
if(ListDelete(L,3,deleteElem)){
printf("已成功删除元素:%d\n",deleteElem);
}else{
printf("位序不合法,删除失败\n");
}
return 0;
}
单链表的定义
什么是单链表
用代码定义一个单链表
不带头结点的单链表
带头结点的单链表
不带头结点v.s.带头结点
单链表的插入删除
关于简化图示的说明
按位序插入(带头结点)
按位序插入(不带头结点)
指定结点的后插操作
指定结点的前插操作
给定头指针
不给头指针
按位序删除(带头结点)
指定结点的删除
封装的好处
单链表的查找
按位查找
- 之前已经实现了按位查找的代码了,只不过只是找到了n-1位,这里只需要改为n位即可
封装(基本操作)的好处
按值查找
求表的长度
单链表的建立
尾插法建立单链表
头插法建立单链表
双链表
单链表 v.s. 双链表
双链表的初始化(带头结点)
双链表的插入
双链表的删除
双链表的遍历
循环链表
循环单链表
- 如果在你的应用场景当中经常需要对表头或者表尾进行操作的时候,当你使用循环单链表的时候可以把头指针指向表尾元素,这样可以大大简化我们的程序
循环双链表
循环双链表的初始化
双链表的插入
双链表的删除
静态链表
什么是静态链表
- 指针指明的是具体的内存地址,而游标只是指明了下个元素的数组下标
用代码定义一个静态链表
- 对于上面那种定义方式的理解:其实SLinkList是一个大小为MaxSize的Node型数组,每个数组元素是Node型的结构体
- 如果用Node来定义一个Node的数组,很难让人联想到这是一个静态链表,但是用SLinkList这种方式就很一目了然,类比LNode和LinkList
对猜想的验证
简述基本操作的实现
初始化
查找&插入&删除
- 设置为 -2 可以方便消除内存中脏数据的影响,便于我们判断哪里还有静态链表的剩余空间
链表的基本操作代码实现
#include<stdio.h>
//定义一个结点
struct LNode{
int data;
struct LNode *next;
};
typedef struct LNode{
int data;
struct LNode *next;
}LNode *LinkList;
//增加一个新的结点
struct LNode *p = (struct LNode *)malloc(sizeof(struct LNode));
//为了使用的方便,做个数据类型重命名
typedef struct LNode LNode;
//增加一个新的结点
LNode *p = (LNode *)malloc(sizeof(LNode));
//删除一个结点
free(p);
//初始化一个单链表(不带头结点)
bool InitList(LinkList &L){
L = NULL;
return true;
}
//初始化一个单链表(带头结点)
bool InitList(LinkList &L){
if(L == NULL){
return false;
}
L -> next = NULL;
return true;
}
//判断单链表是否为空
bool IsEmpty(LinkList L){
if(L -> next == NULL){
return true;
}else{
return false;
}
}
//指定节点的后插操作
bool InsertNextNode(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;
}
//指定结点的前插操作
bool InsertPriorNode(LNode *p,int e){
if(p == NULL){
return false;
}
LNode *s = (LNode *)malloc(sizeof(LNode));
if(s == NULL){
return false;
}
//这里前插操作的实现思路是p后插s然后交换p和s的数据,时间复杂度O(1)
s->next = p->next;
p->next = s;
s->data = p->data;
p->data = e;
return true;
}
//按位序删除(带头结点)
bool ListDelete(LinkList &L,int i,int &e){
if(i<1){
return false;
}
LNode *p;
int j = 0;
p = L;
while(p!=NULL && j<j-1){
p = p->next;
j++;
}
if(p == NULL){
return false;
}
if(p->next = NULL){
return false;
}
LNode *q = p->next;
e = q->data;
p->next = q->next;
free(q);
return true;
}
//指定节点的删除
bool DeleteNode(LNode *p){
if(p == NULL){
return false;
}
LNode *q = p->next;
p->data = q->data;
p->next = q->next;
free(q);
return true;
}
//单链表的按位查找
LNode* GetElem(LinkList L,int i){
int j=1;
LNode *p=L->next;
if(i==0){
return L;
}
if(i<1){
return NULL;
}
while(p!=NULL && j<i){
p = p -> next;
j++;
}
return p;
}
//单链表的按值查找
LNode * LocateElem(LinkList L,int e){
LNode *p = L->next;
while(p!=NULL && p->data != e){
p = p->next;
}
return p;
}
//单链表的按位序插入(带头结点)
bool ListInsert(LinkList &L,int i,int e){
if(i<1){
return false;
}
LNode *p;
int j = 0;
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;
return InsertNextNode(p,e);
}
//单链表的按位序插入(不带头结点)
bool ListInsert(LinkList &L,int i,int e){
if(i<1){
return false;
}
if(i == 1){
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = L;
L = s;
return true;
}
LNode *p;
int 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;
return InsertNextNode(p,e);
}
//求单链表的长度
int Length(LinkList L){
int len = 0;
LNode *p = L;
while(p->next != NULL){
p = p->next;
len++;
}
return len;
}
//尾插法建立单链表
LinkList ListTailInsert(LinkList &L){
int x;
L = (LinkList)malloc(sizeof(LNode));
LNode *s,*r = L;
scanf("%d",&x);
while(x != 9999){
s = (LNode *)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;
scanf("%d",&x);
}
r->next = NULL;
return L;
}
//头插法建立单链表
LinkList ListHeadInsert(LinkList &L){
int x;
LNode *s;
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
scanf("%d",&x);
while(x != 9999){
s = (LNode *)malloc(sizeof(LNode));
s->data = x;
s->next = L->next;
L->next = s;
scanf("%d",&x);
}
return L;
}
//双链表的定义
typedef struct DNode{
int data;
struct DNode *prior;
struct DNode *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;
}
//双链表的插入
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 DeleteNextDNode(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){
DeleteNextDNode(L)
}
free(L);
}
//双链表的遍历
//后向
while(p!=NULL){
p = p->next;
}
//前向
while(p!=NULL){
p = p->prior;
}
//前向且跳过头结点
while(p->prior != NULL){
p = p->prior;
}
int main(){
LinkList L;
InitList(L);
return 0;
}
顺序表和链表的比较
逻辑结构
物理结构/存储结构
数据的运算/基本操作
创
销
- 由malloc函数申请的空间是属于内存当中的堆区,在堆区的内存空间不会由系统自动地回收,需要手动free
- 如果你用声明一个数组或者声明一个变量的方式申请的内存空间,会由系统自动地帮你完成回收工作
增&删
查
用顺序表 or 链表?