第二、三章 线性表、栈、队列和数组

一、数据结构定义

线性表

  1. 顺序存储(顺序表就是将线性表中的元素按照某种逻辑顺序,依次存储到从指定位置开始的一块连续的存储空间,重点是连续的存储空间。与数组非常接近)
    • 静态分配(数组的大小和空间固定,再加入新的数据会产生溢出)
      #define MaxSize 50 //线性表的最大长度
      typedef struct{
      	ElemType data[MaxSize];
      	int length;
      } SqList
      
    • 动态分配(动态分配不是链式存储,是顺序存储结构,依然是随机存取方式,只是分配的空间大小可以在运行时动态决定)
      typedef struct{
      	ElemType *data;
      	int length;  // 顺序表当前存放的元素个数,即表长
      	int size; // 顺序表预分配的存储容量,即最多可以存放的元素个数。当分配的空间存满数据后,可在内存的另一处申请一个更大的空间,将原顺序表全部拷贝到刚申请的空间,并修改size值,最后将指针指向该空间首地址
      } SqList; 
      
  2. 链式存储(链结构的定义就是在结构体内写节点,然后在外面加一个*链名,以指向节点的方式表示整个链,链队除外,因为要额外定义队列层面的首尾节点,所以要专门写个表示整个链队的结构体)
    • 单链表(“单”是指链表结点只存储单个指针)
      typedef struct LinkNode{
      	ElemType data;
      	struct LinkNode *next;
      } LinkNode, *LinkList;
      

      LinkNode*和LinkList是等价的:通常用LinkNode*表示单链表节点的指针变量;用LinkList表示单链表的头指针

    • 双向链表
      typedef struct DLinkNode{
      	ElemType data;
      	struct DLinkNode *prior, *next; // 前驱和后继指针
      } DLinkNode, *DLinkList;
      
    • 循环链表(相比于之前的单链表和双链表,其特点是表中最后一个节点的指针指向头节点,整个链表形成一个环)
      • 循环单链表
        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 = L; // 头节点next指向头节点
        	return true;
        }
        
      请添加图片描述
      在这里插入图片描述
      • 循环双链表
        typedef struct DNode{
        	ElemType data;
        	struct DNode *prior, *next;
        } DNode, *DLinklist;
        
        // 初始化空的循环双链表
        bool InitDLinkList(DLinkList &L){
        	L = (DNode *) malloc(sizeof(DNode));
        	if (L==NULL)
        		return false;
        	L->prior = L;
        	L->next = L;
        	return true;
        }
        
        请添加图片描述
        在这里插入图片描述
    • 静态链表(单链表是用的指针,但是有的编程语言是没有指针这个功能的,可以用数组来代替指针来描述单链表,这种用数组描述的链表就叫静态链表)
      #define MaxSize 10
      typedef struct{
      	ElemType data; // 存储数据元素
      	int next; // 下一个元素的数组下标
      } SLinkList[MaxSize];
      
      在这里插入图片描述
      请添加图片描述
      • 优点:增、删操作不需要大量移动元素
      • 缺点:不能随机存取,只能从头节点开始依次往后查找;容量固定不可变
      • 使用场景:不支持指针的低级语言;数据元素数量固定不变的场景(如操作系统的文件分配表FAT)

  1. 栈的顺序存储(静态数组实现,并需要记录栈顶指针)
    #define MaxSize 50
    typedef struct{
    	Elemtype data[MaxSize];
    	int top; //指向栈顶位置
    }SqStack;
    
  2. 共享栈(顺序存储,两个栈共享同一片空间。栈满条件:top0+1==top1)
    #define MaxSize 10
    typedef struct{
    	ElemType data[MaxSize];
    	int top0; // 0号栈栈顶指针
    	int top1; // 1号栈栈顶指针
    } ShStack;
    
  3. 栈的链式存储(入栈就是在头节点后面插入一个元素,出栈就是把头节点下一个元素删除)
    typedef struct LinkNode{
    	ElemType data;
    	struct LinkNode *next;
    } LinkNode, *LiStack;
    
    请添加图片描述

队列

  1. 顺序存储
    #define MaxSize 50 // 定义队列中元素的最大个数
    typedef struct{
    	ElemType data[MaxSize];
    	int front, rear;
    } SqQueue;
    
  2. 链式存储(头指针指向队首节点的前一个节点,方便删除;尾指针指向队尾节点)
    typedef struct LinkNode{
    	ElemType data; 
    	struct LinkNode *next;
    } LinkNode;
    
    typedef struct{
    	LinkNode *front, *rear; // 队列可由队头指针和队尾指针代表整个链表,因此将队头指针和队尾指针放入一个结构体用来表示队列
    } LinkQueue;
    
  3. 循环队列:是解决了假溢出问题的顺序队列
  4. 双端队列:指在队列的两端都可以进行插入和删除操作的队列

数组

无代码

二、代码/算法

线性表

  1. 顺序表上的基本操作:初始化(静态分配and动态分配)、插入、删除、按位查找、按值查找、遍历输出、顺序表清空、顺序表判空、(动态分配)增加动态数组的长度
  2. 单链表上的操作:
    • 单链表的初始化
      • 有头节点
      • 无头节点(不带头节点,写代码更麻烦。对第一个数据节点和后续数据节点的处理需要用不同的代码逻辑,对空表和非空表的处理需要用不同的代码逻辑)
    • 建立单链表
      • 头插法
      • 尾插法
    • 按位查找节点
    • 按值查找节点
    • 单链表遍历输出
    • 插入节点
    • 删除节点
    • 求表长
    • 单链表清空
    • 判空
    • 原地倒置单链表
    • 双指针遍历单链表:双指针的几个经典用法:
      • 找到链表倒数第k个节点
      • 找到链表中间的节点
      • 判断链表是否有环
  3. 双链表上的基本操作:插入、删除
  4. 循环链表的插入和删除与普通链表区别不大,只需在头尾处进行插入删除操作时要注意维护循环链表循环的特性。若

  1. 顺序栈的基本操作:初始化、判空、判满、进栈、出栈、读栈顶元素
  2. 链栈的基本操作:进栈、出栈
  3. 栈的应用:括号匹配
    bool bracketCheck(char str[], int length){
    	SqStack S;
    	InitStack(S);  // 初始化一个栈
    	for (int i=0; i<length; i++){
    		if (str[i]=="(" || str[i]=="[" || str[i]=="{"){
    			Push(S, str[i]);  // 扫描到左括号,入栈
    		} else{
    			if (StackEmpty(S)) // 扫描到右括号时,当前栈空,就匹配失败
    				return false;
    			
    			char topElem;
    			Pop(S, topElem);  // 栈顶元素出栈
    			if (str[i]==")" && topElem!="(")
    				return false;
    			if (str[i]=="]" && topElem!="[")
    				return false;
    			if (str[i]=="}" && topElem!="{")
    				return false;
    		}
    	}
    	return StackEmpty(S);  // 检索完全部括号后,栈空说明匹配成功
    }
    
  4. 栈的应用:中缀表达式转后缀表达式
    • 初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。从左往右扫描每个元素
    • 若遇到操作数,则直接加入后缀表达式
    • 若遇到界限符,遇“(”则直接入栈;遇“)”则依次弹出栈内运算符并加入后缀表达式,直到弹出“(”为止。
    • 若遇到运算符,则依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式。若碰到“(”或栈空则停止。之后再把当前运算符入栈
    • 按照上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式
  5. 栈的应用:后缀表达式求值(这里栈是用于存放当前暂时还不能确定运算次序的操作数)
    • 先将表达式化为后缀表达式,从左往右扫描每一个元素
    • 若扫描到操作数,就压入栈,并继续扫描
    • 若扫描到运算符,就弹出两个栈顶元素,执行相应运算(先出栈的是右操作数),运算结果压回栈顶
  6. 栈的应用:中缀表达式计算
    • 初始化两个栈,操作数栈和运算符栈
    • 若扫描到操作数,压入操作数栈
    • 若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈(期间每弹出一个运算符,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)

队列

  1. 不同队列的操作:初始化、判队空、判队满、入队、出队
    • 链式存储下,一般不会队满,除非内存不足

不同数据结构的对比

  1. 顺序表 VS 链表
    • 逻辑结构:都属于线性表,都是线性结构
    • 物理结构(存储结构)
      • 顺序表是顺序存储,支持随机存取、存储密度高。但大片连续空间分配不方便,改变容量不方便
      • 链表是链式存储,离散的小空间分配方便,改变容量方便。但不可随机存取,存储密度低
    • 数据的运算和操作
      • 创建:
        • 顺序表需要预先分配大片连续空间(静态分配容量不可改变,动态分配容量可改变,但要移动大量元素,时间代价高);
        • 链式存储只需分配一个头节点(也可不要头节点,只声明一个头指针)
      • 销毁:
        • 顺序表:静态分配为系统自动回收空间;动态分配需要手动free(删除)
        • 链表:需要依次free各个节点
      • 插入/删除元素
        • 顺序表:需要将后序元素都后移/前移,时间复杂度 O ( n ) O(n) O(n)(主要来源于移动元素)
        • 链表:只需修改指针,时间复杂度 O ( n ) O(n) O(n)(主要来源于查找目标元素)
      • 查找
        • 顺序表:按位查找 O ( 1 ) O(1) O(1);按值查找 O ( n ) O(n) O(n)
        • 链表:按位查找 O ( n ) O(n) O(n);按值查找 O ( n ) O(n) O(n)
    • 综上,若表长难以预估、经常要增加/删除元素,用链表;若表长可预估、查询(搜索)操作较多则用顺序表
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值