数据结构第2章-线性表

目录

线性结构与非线性结构

1.线性结构 

2.非线性结构

稀疏数组和队列

1.稀疏数组

2.队列

1.数组模拟队列

2.用链表模拟队列

栈(stack)

1.栈的应用场景

2.用数组模拟栈的使用

前缀 中缀 后缀表达式

1.前缀(波兰表达式)

2.中缀

3.后缀(逆波兰表达式)

4.逆波兰计算器(这里输入的是后缀表达式,真正的计算器还应实现 中缀表达式转换为后缀表达式)

5.中缀表达式转换为后缀表达式


2.1 线性表的定义和特点

由n (n≥0)个数据特性相同的元素构成的有限序列称为线性表,(n=0)的时候被称为空表。

 

2.2 线性表的类型定义

 

2.3 线性表的顺序表示和实现

2.3.1 线性表的顺序表示

线性表的顺序存储又被称为顺序表

顺序存储表示:用一组地址连续的存储单元依次存储线性表的数据元素的方式,具有顺序存储结构的特点(数据间的逻辑关系和物理关系是一致的)

假设线性表 L 存储的起始位置为 LOC(A) ,sizeof(ElemType) 是每个数据元素所占用存储空间的大小,则表 L 所对应的顺序存储如下图:

由于线性表的长度可变,且所需最大存储空间随问题的不同而不同,在C语言中通常使用动态分配的一维数组表示线性表

//----- 顺序表的存储结构-----
#define MAXSIZE 100             //顺序表可能达到的最大长度
    typedef struct {
        ElemType *elem;         //存储空间的基地址
        int length;             //当前长度
    } SqList;                   //顺序表的结构类型为SqList

2.3.2 顺序表中基本操作的实现

一、顺序表初始化
①、为顺序表L动态分配一个预定义大小的数组空间,使elem指向这段空间的基地址。
②、将表的当前长度设为0。

  Status InitList(SqList &L) {
        //构造一个空的顺序表 L
        L.elem = new ElemType[MAXSIZE];     //为顺序表分配一个大小为MAXSIZE的数组空间,这是C++语句
        if (!L.elem) exit(OVERFLOW);        //存储分配失败退出
        L.length = O;                       //空表长度为0
        return OK;
    }

二、顺序表取值

①、判断指定的位置序号 i 值是否合理 (1≤i≤L.length), 若不合理,则返回ERROR。

②、若 i 值合理,则将第 i 个数据元素 L.elem[i-1] 赋给参数 e, 通过 e返回第 i 个数据元素的传值。

  Status GetElem(SqList L, int i, ElemType &e) {
        if {
            (i < 1 || i > L.length) return ERROR;      //判断l. 值是否合理,若不合理, 返回 ERROR
            e = L.elem[i - 1];                         //elem[i-1] 单元存储第 i 个数据元素
            return OK;
        }

    }

三、顺序表的按值查找

①、从第一个元素起,依次和 e相比较,若找到与 e相等的元素 L.elem[i], 则查找成功,返回该元素的序号 i+l (数组下标从 0 开始)

②、若查遍整个顺序表都没有找到,则查找失败, 返回0

int LocateELem(SqList L, ElemType e) {
        //在顺序表1中查找值为e的数据元素, 返回其序号
        for (i = O; i < L.length; i++) {
            if (L.elem[i)==e){
                return i + l;           //查找成功, 返回序号 i+l
            }
        }
        return O;                       //查找失败, 返回 0           
    }

  

四、顺序表插入元素

在第 i 个位置插入一个元素时,需从最后一个元素即第 n 个元素开始,依次向后移动一个位置,直至第 i 个元素。

 

①、判断插入位置 i 是否合法(i 值的合法范围是 1≤i≤n+ I), 若不合法 则返回 ERROR。

②、判断顺序表的存储空间是否已满,若满则返回 ERROR。

③、将第n个至第 i 个位置的元素依次向后移动一个位置,空出第 i 个位置 ( i =n+l 时无需移动)。

④、将要插入的新元素e放入第i个位置。

⑤、表长加1

Status Listinsert(SqList &L, int i, ElemType e) {
        //在顺序表 L 中第 i 个位置之前插入新的元素 e, i值的合法范围是 1...i...L.length+l
        if ((i < l) || (i > L.length + l)) {
            return ERROR;                       //i值不合法
        }
        if (L.length == MAXSIZE) {
            return ERROR;                       //当前存储空间已满
        }
        for (j = L.length - 1; j >= i - 1; j--) {
            L.elem[j + l] = L.elem[j];                     //插入位置及之后的元素后移
        }
        L.elem[i - l] = e;                                //将新元素e放入第l个位置
        ++L.length;                                       //表长加1
        return OK;
    }

五、顺序表删除元素

删除第 i 个元素时需将第 i+ 1 个至第 n 个元素依次向前移动一个位置 (i = n 时无需移动)

 

①、判断删除位置 i 是否合法(合法值为 1 ≤ i ≤n), 若不合法则返回 ERROR。

②、将第 i个至第n个的元素依次向前移动一个位置 (i = n时无需移动)

③、表长减 1

Status ListDelete(SqList &L, int i) {
        //在顺序表L中删除第J.个元素,J.值的合法范围是 1.;;i.;;L. length
            if ((i < l) || (i > L.length)) {
                return ERROR;                               //i值不合法
            }
            for (j = i; j <= L.length - 1; j++) {
                L.elem[j - 1]=1.elem[j];                    //被删除元素之后的元素前移
            }
            --L.length;                                     //表长减 1
            return OK;
        }

 

2.4 线性表的链式表示和实现

链式存储结构的特点:用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的),存放数据元素的结点至少包括两个域(指针域、数据域),也可以包含若干个指针域和数据域。

2.4.1 单链表的定义和表示

关于带头结点的单链表及不带头节点的单链表

  • 首元结点:是指链表中存储第一个数据元素的结点。
  • 头结点:是在首元素结点之前附设的一个结点,其指针指向首元结点。
  • 头指针:是指向链表中第一个结点的指针。若链表设有头结点,则头指针所指结点为线性表的头结点;若链表不设头结点,则头指针所指结点为该线性表的首元结点。

带头结点:

 不带头结点:

  • 带头结点的单链表为空时:L->next==NULL;
  • 不带头结点的单链表为空时:L=NULL;
    //----- 单链表的存储结构-----
    typedef struct LNode {
        ElemType data;          //结点的数据域
        struct LNode *next;     //结点的指针域
    } LNode, *LinkList;         //LinkList 为指向结构体 LNode 的指针类型

2.4.2 单链表基本操作和实现

2.4.3 循环链表

2.4.3 双向链表

稀疏数组和队列

1.稀疏数组

当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组

处理方法:

(1)记录数组一共有几行几列,有多少个不同的值 

(2)把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模

二维数组和稀疏数组 互相转换

public static void main(String[] args){
//二维数组展示为棋盘
    int chessArr1[][] = new int[11][11];
    chessArr1[1][2] = 1; //黑子
    chessArr1[2][3] = 2; //蓝子
    for(int [] row : chessArr1){
        for(int data: row){
            System.out.printf("%d\t",data);
        }
        System.out.println();
    }

//二维数组 转 稀疏数组
    //遍历二维数组得到非0数值的个数
    int sum= 0;
    for(int i =0; i < 11; i++){
        for(int j =0; j < 11; j++){
            if(chessArr1[i][j] != 0){
                sum++;
            }        
        }
    }
    //创建对应的稀疏数组
    int sparseArr[][] = new int[sum+1][3];
    sparseArr[0][0] = 11;
    sparseArr[0][1] = 11;
    sparseArr[0][2] = sum;
    //将二维数组中不是0的值赋给稀疏数组
    int count = 1; //用来记录是第几个不是零的值
     for(int i =0; i < 11; i++){
        for(int j =0; j < 11; j++){
            if(chessArr1[i][j] != 0){
                sparseArr[count][0] = i;
                sparseArr[count][1] = j;
                sparseArr[count][2] =chessArr[i][j];
            }        
        }
    }
    //输出稀疏数组的形式
    System.out.println();
    for(int i = 0;i < sparseArr.length; i++){
        System.out.println("%d\t%d\t%d\n", sparseArr[i][0],sparseArr[i][1],sparseArr[i][2])
    System.out.println();
    }   

    //稀疏数组 恢复成 二维数组
    int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];
    for(int i = 1; i < sparseArr.length; i++){
        chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i]][2];
    }
}                                                                       

2.队列

队列是一个有序列表,可以用数组或者链表来实现

遵循先入先出的原则数

1.数组模拟队列

class ArrayQueue{
    private int maxSize;
    private int front;
    private int rear;
    private int[] arr; //该数组用于存放数据,模拟队列

    //队列构造器
    public ArrayQueue(int arrMaxSize){
        maxSize = arrMaxSize;
        arr = new int[maxSize];
        front = -1;
        rear = -1;
    }
    //判断队列是否为满
    public boolean isFull(){
        return rear == maxSize - 1;
    }
    //判断队列是否为空
    public boolean isEmpty(){
        return rear == front;
    }
    //添加数据到队列
    public void addQueue(int n){
        if(isFull()){
            System.out.println("队列已满,无法添加");
            return;
        }
        rear++;
        arr[rear] = n;
    }
    //获取数据出队列 只能获取队列最前面的
    public int getQueue(){
        if(isEmpty()){
            System.out.println("队列空,无法获取");
        }
        front++;
        return arr[front];
    }
    //显示队列所有数据 注意是显示不是不是取出
    public void showQueue(){
        if(isEmpty()){
            System.out.println("队列空,没有数据显示");
            return;
        }
        for(int i = 0; i < arr.length; i++){
            System.out.printf("arr[%d] = %d\n", i, arr[i]);
        }
    }
    //显示队列头部数据
    public int headQueue(){
        if(isEmpty()){
            System.out.println("队列空,没有数据显示");
        }
        return arr[front + 1];
    }
}

问题分析并优化

(1)目前数组使用一次就不能使用了

(2)将这个数组改成一个环形队队列,取模:%

 

class CircleArray{
    private int maxSize;
    //front 变量含义做一个调整:front 就指向第一个元素,而不是第一个元素前一个
    private  int front; //front = 0;
    //rear 变量含义做一个调整: rear指向队列的最后一个位置的后一个位置,因为希望空出一个位置作为约定
    private int rear; //rear = 0;
    private int arr[];

    //构造器
    public CircleArray(int arrMaxSize){
        maxSize = arrMaxSize;
        arr = new int [maxSize];
    }
    //判断队列是否已满
    public boolean isFull(){
        return (rear + 1) % maxSize == front;
    }
    //判断队列是否为空
    public boolean isEmpty(){
        return rear == front;
    }
    public void addQueue(int n){
        if(isFull()){
            System.out.println("队列已满,无法添加");
            return;
        }
        arr[rear] = n;
        rear = (rear + 1) % maxSize;  //让rear最后一个元素再后移一个位置
    }
    public int getQueue(){
        if(isEmpty()){
            throw  new RuntimeException("队列空,无法取出数据");
        }
        //1.先把front对应的值保存到一个临时变量
        //2.将front后移
        //3.将临时保存的变量返回
        int value = arr[front];
        front = (front + 1) % maxSize;
        return value;
    }
    //求出当前队列有效存储位置的个数
    public int getSize(){
        return (rear + maxSize - front) % maxSize;
    }
    //显示队列所有数据 
    public void showQueue() {
        if(isEmpty()){
            throw  new RuntimeException("队列空,无法取出数据");
        }
        for (int i = front; i < getSize() + front; i++){
            System.out.printf("arr[%d]=%d", i % maxSize, arr[i % maxSize]);
        }
    }
    //显示队列的头数据,注意不是取出数据
    public int headQueue(){
        if (isEmpty()){
            throw new RuntimeException("队列空,没有数据");
        }
        return arr[front];
    }
}

2.用链表模拟队列

链表是以节点的方式来存储的,每个节点都包含data域,next域:指向下一个节点

(1)双向链表

每个节点包含data域,next域,pre域(指向前一个节点)

 (2)单向环形链表

应用场景:约瑟夫问题

 例子:

       

class CircleSingleLinkedList {
    //创建一个first节点
    public Boy first = null;

    //添加一个小孩节点,构成一个环形的链表
    public void addBoy(int nums) {
        //添加nums个节点来构建环形链表
        if (nums < 1) {
            System.out.println("nums的值不正确");
            return;
        }
        Boy cur = null; //辅助指针
        //使用for循环来构建环形链表
        for (int i = 0; i <= nums; i++) {
            Boy boy = new Boy(i);
            //如果是只有一个节点的环形链表,特殊情况
            if (i == 1) {
                first = boy;
                first.next = first;
                cur = first;
            } else {
                cur.next = boy;
                boy.next = first;
                cur = boy;
            }
        }
    }

栈(stack)

出栈(pop) 入栈(push)

1.栈的应用场景

2.用数组模拟栈的使用

class ArrayStack {
    public int maxSize; //栈的大小
    public int[] stack; //该数组用来模拟栈
    public int top = -1; //表示栈顶,初始默认-1 表示没有值

    //构造器 创建一个名为stack的数组 大小为maxSize用来模拟栈
    public ArrayStack(int maxSize) {
        this.maxSize = maxSize;
        stack = new int[this.maxSize];
    }
    //栈满
    public boolean isFull() {
        return top == maxSize - 1;
    }
    //栈空
    public boolean isEmpty() {
        return top == -1;
    }
    //push
    public void push(int value) {
        if(isFull()) {
            System.out.println("栈满");
            return;
        }
        top++;
        stack[top] = value;
    }
    //pop
    public int pop() {
        if(isEmpty()){
            throw new RuntimeException("栈空");
        }
        int value = stack[top];
        top--;
        return value;
    }
    //遍历栈 从栈顶开始
    public void show() {
        if(isEmpty()) {
            System.out.println("没有数据");
            return;
        }
        for(int i = top; i >= 0; i--) {
            System.out.printf("stack[%d]=%d", i, stack[i]);
            System.out.println();
        }
    }
}

前缀 中缀 后缀表达式

1.前缀(波兰表达式)

 

2.中缀

3.后缀(逆波兰表达式)

 

 

4.逆波兰计算器(这里输入的是后缀表达式,真正的计算器还应实现 中缀表达式转换为后缀表达式)

package com.tt12.stack;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;


//逆波兰计算器(从左往右扫描)
public class PolanNotation {
    public static void main(String[] args) {
        //(3+4)×5-6 => 3 4 + 5 × 6 -
        //说明:为了方便,逆波兰表达式的数字和符号使用空格隔开
        String suffixExpression = "3 4 + 5 × 6 -";
        //1.先将suffixExpression放入ArrayList中
        //2.将ArrayList 传递给一个方法,配合栈完成计算
        List<String> list = getListString(suffixExpression);
        System.out.println("rpnlist = " + list);
        int res = calculate(list);
        System.out.println(res);

    }

    //将逆波兰表达式放入ArrayList中的方法
    public static List<String> getListString(String suffixExpression) {
        //将suffixExpression分割
        String[] split  = suffixExpression.split(" ");
        List<String> list = new ArrayList<String>();
        for(String ele: split) {
            list.add(ele);
        }
        return list;
    }

    //完成计算的方法
    public static int calculate(List<String> ls) {
        Stack<String> stack = new Stack<String>();
        for (String item : ls) {
            //使用正则表达式
            if (item.matches("\\d+")) { //匹配的是多位数
                //直接入栈
                stack.push(item);
            } else {
                //不是多位数是符号 则pop出两个数 再将运算结果入栈
                int num1 = Integer.parseInt(stack.pop());
                int num2 = Integer.parseInt(stack.pop());
                int res = 0;
                if (item.equals("+")) {
                    res = num1 + num2;
                } else if (item.equals("-")) {
                    res = num2 - num1;
                } else if (item.equals("×")) {
                    res = num1 * num2;
                } else if (item.equals("÷")) {
                    res = num1 / num2;
                }
                else {
                    throw new RuntimeException("运算符有误");
                }
                System.out.println(res);
                stack.push(String.valueOf(res));
            }
        }
        //最后留在栈中的数据即答案
        return Integer.parseInt(stack.pop());
    }
}

5.中缀表达式转换为后缀表达式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值