王道数据结构笔记【线性表(顺序表、链表)】

本文详细介绍了顺序表和链表的数据结构,包括静态与动态分配的顺序表操作,如插入、删除和查找,以及单链表、带头结点链表和双链表的构建、修改和查找。深入探讨了算法的时间复杂度和空间复杂度,并展示了循环链表的应用实例。
摘要由CSDN通过智能技术生成

数据结构的基本概念

数据结构三要素: 1.逻辑结构 2. 存储结构(物理)3. 数据的运算

在这里插入图片描述

算法的基本概念

  • 程序=数据结构+算法
  • 算法必须得保证有限时间内能执行完当前问题,无论执行的结果对错
  • 若程序不断输入,算法不断解决,程序运行的时间可以是无穷的,但每一次算法解决问题的时间是有穷的
    在这里插入图片描述

算法的时间复杂度

  • 忽略常数项和系数,只关注数量级
  • 一般最好时间复杂度不做参考。考虑平均和最坏的情况
  • 巴啦啦能量—常对幂指阶
    在这里插入图片描述

算法的空间复杂度

在这里插入图片描述

线性表

线性表为一种逻辑结构(指具有线性序列)

  • 定义:线性表为具有相同(每个数据元素所占空间一样大)数据类型的n个数据元素的有限序列(有次序)

eg.所有整数按递增次序排列,不是线性表。—因为线性表是有限序列

+++

  • 线性表的基本操作

  • InitList(&L)创:初始化表,为其分配空间

  • DestroyList(&L)销毁表,释放空间

  • ListInsert(&L,i,e)插入元素

  • ListDelete(&L,i,&e)删除元素

  • LocateElem(L,e)按值查找在这里插入图片描述

  • GetElem(L,i)按位查找

  • Length(L)求表长

  • PrintList(L)输出表

  • Empty(L)判空

创销、增删改查

函数名可改,但如上名字更规范,改卷更合适

顺序表

顺序存储的线性表

静态分配的顺序表
#define MaxSize 10
typedef struct{
  ElemType data[MaxSize];//Elemtype可以是int/struct..类型
  int length;
}SqList;

容量大小不可变

动态分配的顺序表
#define InitSize 10//顺序表的初始长度
typedef struct{
  ElemType *data;//Elemtype可以是int/struct..类型
  int MaxSize;//顺序表的最大容量
  int length;//顺序表的当前长度
}SqList;//顺序表的类型定义

动态申请空间(malloc&realloc)

malloc函数–申请一块连续空间

L.data=(ElemType *)malloc(sizeof(ElemType)*InitSize);
\\需要进行强制类型转换

释放内存空间:

free函数

示例1.实现顺序表的初始化、输入、扩大动态数组

#include<stdio.h>
#include<stdlib.h>
#define InitSize 10//顺序表的初始长度
typedef struct {
	int *data;//Elemtype可以是int/struct..类型
	int MaxSize;//顺序表的最大容量
	int length;//顺序表的当前长度
} SqList; //顺序表的类型定义
void InitList(SqList &L);
void IncreaseSize(SqList &L,int add);
int in(SqList &L);
int main() {
	SqList L;
	InitList(L);
	L.length = in(L);
	int add;
	scanf("%d", &add);
	IncreaseSize(L, add); //增加顺序表的长度
	return 0;
}
void InitList(SqList &L) { //初始化顺序表
	L.data = (int *)malloc(sizeof(int) * InitSize); 
	L.MaxSize = InitSize;
	L.length = 0;
}
int in(SqList &L) { //输入顺序表
	int i;
	for (i = 0; i < L.MaxSize; i++)
		scanf("%d", &L.data[i]);
	return i;
}
void IncreaseSize(SqList &L,int add){
	int *p=L.data;//int *类型
	L.data=(int *)malloc(sizeof(int)*(L.MaxSize+add));//开辟一块新内存
	for(int i=0;i<L.length;i++){
		L.data[i]=p[i];//将原数据复制到新区域
	}
	L.MaxSize+=add;
	free(p);//释放原内存
}

在这里插入图片描述

总结–顺序表的特点

  1. 可随机访问,即可在O(1)时间内找到第i个元素
  2. 存储密度高,每个节点只存储数据元素(链表存储密度较低)
  3. 拓展容量不方便(静态不允许。动态麻烦,时间复杂度高)
  4. 插入删除不方便,需要移动大量元素

在这里插入图片描述

1.顺序表的插入(静态分配情况下)
#include<stdio.h>
#include<stdlib.h>
#define MaxSize 10//顺序表的初始长度
typedef struct {
	int data[MaxSize];
	int length;//顺序表的当前长度
} SqList; //顺序表的类型定义
void input(SqList &L, int l);
bool ListInsert(SqList &L, int i, int e);
void output(SqList &L,int l);
int main() {
	SqList L;
	scanf("%d", &L.length);
	input(L, L.length);
	if (ListInsert(L, 3, 16)) {
		printf("已向第3个位序插入元素16\n");
	} else {
		printf("操作不合法\n");
	}
	output(L,L.length);
	return 0;
}
void input(SqList &L, int l) { //输入顺序表
	for (int i = 0; i < l; i++)
		scanf("%d", &L.data[i]);
}
bool ListInsert(SqList &L, int i, int e) { //在数组第i-1个下标插入e
	if (i < 1 || i > L.length)//不合法位置
		return false;
	if (L.length >= MaxSize)//顺序表已满
		return false;
	for (int j = L.length; j >= i; j--) {
		L.data[j] = L.data[j - 1];
	}
	L.data[i - 1] = e;
	L.length++;//易漏
	return true;
}
void output(SqList &L,int l){
	for(int i=0;i<l;i++)
	printf("%d ",L.data[i]);
}

在这里插入图片描述

2.顺序表的删除(静态分配情况下)
#include<stdio.h>
#include<stdlib.h>
#define MaxSize 10//顺序表的初始长度
typedef struct {
	int data[MaxSize];
	int length;//顺序表的当前长度
} SqList; //顺序表的类型定义
void input(SqList &L, int l);
bool ListDelete(SqList &L, int i, int &e);
void output(SqList &L, int l);
int main() {
	SqList L;
	scanf("%d", &L.length);
	input(L, L.length);
	int m=-1;
	if (ListDelete(L, 3, m)) {
		printf("已删除第3个位序\n");
	} else {
		printf("操作不合法,删除失败\n");
	}
	printf("%d\n", m);
	output(L, L.length);
	return 0;
}
void input(SqList &L, int l) { //输入顺序表
	for (int i = 0; i < l; i++)
		scanf("%d", &L.data[i]);
}
bool ListDelete(SqList &L, int i, int &e) { //在删除数组第i-1个下标对应的元素,并将其返回给e
	if (i < 1 || i > L.length)//不合法位置
		return false;
	e = L.data[i - 1];
	for (int j = i-1; j <= L.length - 2; j++) {
		L.data[j] = L.data[j + 1];
	}
	L.length--;//易漏
	return true;
}
void output(SqList &L, int l) {
	for (int i = 0; i < l; i++)
		printf("%d ", L.data[i]);
}

在这里插入图片描述

在这里插入图片描述

顺序表的查找
按位查找

1.静态分配(略)

2.动态分配

#define InitSize 10
typedef struct{
  ElemType *data;
  int MaxSize;//最大容量
  int length;//当前长度
}SqList;
ElemType GetElem(SqList L,int i){//查找第i个位序的元素
  return L.data[i-1];
}
按值查找

基本思想:==遍历判等

例外–结构体查找

判断结构体a,b相等,不可用==

需挨个判等

错误示范

typedef struct l{
  int num;
  int people;
}Customer;
void test(){
  Customer a;
  a.num=1;
  a.people=2;
  Customer b;
  b.num=1;
  b.people=2;
  if(a==b){//甚至无法通过编译
    printf("相等");
  }
}

在这里插入图片描述

链表

链式存储的线性表

在这里插入图片描述

单链表

单链表的定义

typedef struct LNode{
  ElemType data;
  struct LNode *next;
}LNode,*LinkList;
LNode *L;//声明一个指向单链表第一个结点的指针
或
LinkList L;//等效,代码可读性更强

LNode *强调是一个结点

LinkList强调是个单链表

例1.获取链表中第i个结点

typedef struct LNode{
  ElemType data;
  struct LNode *next;
}LNode,*LinkList;
LNode * GetElem(LinkList L,int i){//为LinkList,强调传入的是一个单链表
  int j=1;
  LNode *p=L->next;
  if(i==0)
    return L;
  else if(i<1)
    return NULL;
  else {
    while(p!=NULL&&j<i){
      p=p->next;
      j++;
    }
  }
  return p;//函数类型为LNode *强调返回的是一个结点
}
不带头结点的单链表
typedef struct LNode{
  ElemType data;
  struct LNode *next;
}LNode,*LinkList;
bool InitList(LinkList &L){//要用引用类型 **重点理解
  L=NULL;//空表,暂时无结点
  return true;
}
void test(){
  LinkList L;//声明一个指向单链表的指针
  InitList(L);
}

重点理解

LinkList L 本身为结构体指针

不用&传入:改变 L指针指向的地址所存储的元素

用&传入:可改变L指针指向的地址

LinkList LLinkList &L都可改变链表内部的指向

区别在于,加了&可改变头指针的指向

类比

int b=1;
int *a=&b;
f1(a);
void f1(int *x){
  *x=2;
}//*a=b=2

int b=1;
int *a=&b;
f2(a);
void f2((int *)&a){
  a=NULL;
}//b=1,a=NULL

在这里插入图片描述

带头结点的单链表
typedef struct LNode{
  ElemType data;
  struct LNode *next;
}LNode,*LinkList;
bool InitList(LinkList &L){//要用引用类型 **重点理解
  L=(LNode *)malloc(sizeof(LNode));//分配一个头结点,头结点不存储数据
  if(L==NULL){
    printf("内存不足,分配失败");
    return false;
  }
  else
    L->next=NULL;//头结点后暂无其他结点
  return true;
}
void test(){
  LinkList L;//声明一个指向单链表的指针
  InitList(L);
}

在这里插入图片描述

在这里插入图片描述

在第i个位置插入e(带头结点)
bool ListInsert(LinkList &L,int i,int e){//在第i个位置插入元素e
  if(i<1);
  return false;
  LNode *p;
  p=L;//L指向头结点,头结点为第0个结点
  int j=0;
  while(p!=NULL&&j<i){
    p=p->next;
    j++;
  }
  if(p==NULL)//i的值大于原链表长度
    retrun false;
  LNode *s=(LNode *)malloc(sizeof(LNode));
  s->data=e;
  s->next=p->next;
  p->next=s;
  return true;//插入成功
}

有头结点链表好处:可以在第1个位置插入元素

在第i个位置插入e(不带头结点)
bool ListInsert(LinkList &L,int i,int e){//在第i个位置插入元素e
  if(i<1);
  return false;
  if(i==1){
    LNode *s=(LNode *)malloc(sizeof(LNode));
    s->data=e;
    s->next=L;
    L=s;//头指针指向新结点,改变L的指向
    return true;
  }
  
  LNode *p;
  p=L;//L指向头结点,头结点为第0个结点
  int j=0;
  while(p!=NULL&&j<i-1){
    p=p->next;
    j++;
  }
  if(p==NULL)//i的值大于原链表长度
    retrun false;
  LNode *s=(LNode *)malloc(sizeof(LNode));
  s->data=e;
  s->next=p->next;
  p->next=s;
  return true;//插入成功
}

从此默认带头结点

指定结点的前插(带头结点)
bool InsertPriorNode(LNode *p,ELemType e){//无需改变L的指向,用LinkList p
  if(p==NULL)
    return false;
  LNode *s=(LNode *)malloc(sizeof(LNode));
  if(s==NULL)//内存分配失败
    return false;
  s->next=p->next;
  s->data=p->data;
  p->next=s;
  p->data=e;
  return true;
}

在这里插入图片描述

按位序删除(带头结点)

并带回删除结点数据

bool ListDelete(LinkList &L,int i,ElemType &e){
  if(i<1)
    return false;
  LNode *p;
  p=L;//L指向头结点
  int j=0;
  while(p!=NULL&&j<i-1){//找到第i-1个结点
    p=p->next;
    j++;
  }
  if(p==NULL||p->next==NULL)//i值超过原链表长度 或 第i-1个结点后无其他结点
    return false;
  LNode *q;
  q=p->next;
  e=q->data;//用e返回被删除元素数据
  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;
}
//若p为最后一个结点,只能从表头依次寻找p的前驱

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

按位查找

O(n)

LNode *GetElem(LinkList L,int i){
  if(i<0)
    return NULL;
  LNode *p=L;
  int j=0;
  while(p!=NULL&&j<i){//找到第i个结点
    p=p->next;
    j++;
  }
  return p;
}
按值查找

O(n)

LNode *LocateElem(LinkList L,ELemType e){
  LNode *p=L->next;
  while(p!=NULL&&p->data!=e){
    p=p->next;
  }
  return p;
}

当不能找到对应e的结点时,返回NULL

求表长

O(n)

int length(LinkList L){
  int len=0;
  LNode *p=L;
  while(p->next!=NULL){
    p=p->next;
    len++;
  }
  return len;
}

在这里插入图片描述

单链表的建立
  1. 初始化一个单链表
  2. 每次取一个数据元素,插入表尾(尾插法)、表头(头插法)
初始化一个单链表
bool InitList(LinkList &L){
  L=(LNode *)malloc(sizeof(LNode));//分配一个头结点
  if(L=NULL)//内存不足,分配失败
    return false;
  L->next=NULL;
  return true;
}
尾插法建立单链表
  1. 初始化单链表
  2. 设置变量length记录链表长度

时间复杂度:O(n2)

int x;
int length=0;
while(){
  scanf("%d",&x);
  ListInsert(L,length+1;e);//插到尾部
  length++;
}
bool ListInsert(LinkList &L,int i,ELemType e){//在i位置插入元素e
  if(i<1)
    return false;
  LNode *p;
  int j=0;
  p=L;
  while(p!=NUll&&j<i-1){//找到第i-1个结点
    p=p->next;
    j++;
  }
  if(p==NULL)//i值不合法,超过链表长度
    return false;
  LNode *s=(LNode *)malloc(sizeof(LNode));
  s->data=e;
  s->next=p->next;
  p->next=s;
  return true;
}

课本上的头插法:

LinkList List_Taillnsert(LinkList &L){
  L=(LNode *)malloc(sizeof(LNode));//初始化单链表
  LNode *s,*r=L;
  int x;
  scanf("%d",&x);
  while(x!=9999){//后插操作
    s=(LNode *)malloc(sizeof(LNode));
    s->data=x;
    r->next=s;
    r=s;//永远保持r指向最后一个结点
    scanf("%d",&x);
  }
  r->next=NULL;
  return L;
}
头插法建立单链表
  1. 初始化单链表
int x;
while(){
  scanf("%d",&x);
  InsertNextNode(L,e);//每次对头结点后插入e
}

后插操作:在p结点后插入e

bool InsertNextNode(LNode *p,ElemType 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;
}

课本头插法

LinkList List_HeadInsert(LinkList &L){
  L=(LNode *)malloc(sizeof(LNode));
  L->next=NULL;//初始为空链表
  int x;
  scanf("%d",&x);
  LNode *s;
  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{
  ElemType data;
  struct DNode *prior,*next;//前驱、后继指针
}DNode,*DLinkList;
双链表的初始化

带头结点

bool InitDlinkLis(DLinkList &L){
  L=(DNode *)malloc(sizeof(DNode));//分配一个头结点
  if(L==NULL)
    return false;
  L->prior=NULL;
  L->next=NULL;
  return true;
}

void main(){
  DLinkList L;
  InitDLinkList(L);
}

判断双链表是否为空

bool Empty(DLinkList L){//判断双链表是否为空表
  if(L->next==NULL)
    return true;//为空
  else
    return false;
}
双链表的插入

后插操作

bool InsertNextDNode(DNode *p,DNode *s){//在p结点后插入S结点
  if(p==NULL||s==NULL)//非法参数
    return false;
  s->next=p->next;
  if(p->next!=NULL)//如果p有后继结点
    p->next->prior=s;
  s->prior=p;
  p->next=s;
}

在这里插入图片描述

双链表的删除
bool DeleteNextDNode(DNode *p){//删除p的后继结点q
  if(p==NULL)//非法参数
    return false;
  DNode *q=p->next;
  if(q==NULL)//p没有后继结点
    return false;
  p->next=q->next;
  if(q->next!=NULL)//q不是最后一个结点
  q->next->prior=p;
  free(q);
  return true;
}

销毁双链表

bool DestroyList(DLinkList &L){
  while(L->next!=NULL){
    DeleteDNode(L);//删除头结点的下一个结点
  }
  free(L);//释放头结点
  L=NULL;//头指针指向NULL
}

在这里插入图片描述

循环链表

在这里插入图片描述

循环单链表
初始化一个循环单链表
bool InitList(LinkList &L){
  L=(LNode*)malloc(sizeof(LNode));
  if(L==NULL)//内存不足,分配失败
    return false;
  L->next=L;//头结点的next指向头结点
}

判断结点p是否为循环单链表的表尾结点

bool isTail(LinkList L,LNode *p){
  if(p->next==L)//不同之处
    return true;
  else
    return false;
}
循环单链表的巧妙应用

在这里插入图片描述

若需要对链表的头部和尾部进行多次操作

可考虑将头指针指向链表尾部

插入删除时可能需要移动L

循环双链表

在这里插入图片描述

初始化一个循环双链表
bool InitDLinkList(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;
}
双链表的插入
bool InitDNextDNode(DNode *p,DNode *s){//在p结点后插入s结点
  s->next=p->next;
  p->next->prior=s;
  s->prior=p;
  p->next=s;
}
双链表的删除
bool DeleteNextDNode(DNode *p){//删除p结点的下一结点q
  p->next=q->next;
  q->next->prior=p;
  free(q);
}
  • 0
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快苏排序OAO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值