《Java数据结构入门》顺序表详解

Posted 是小鱼儿哈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Java数据结构入门》顺序表详解相关的知识,希望对你有一定的参考价值。

 大家好,我是小鱼儿

目录

顺序表介绍:

顺序表的手动实现

顺序表功能接口概览

基本功能的实现

四大功能

一、增加数据 

二、删除数据

三、查找数据

四、修改数据

 总代码

MyArraysList.java

 Test.java


顺序表介绍:

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

🍑顺序表一般可以分为:

  1. 静态顺序表:使用定长数组存储元素。(本篇主要围绕静态顺序表展开)
  2. 动态顺序表:使用动态开辟的数组存储。


顺序表的手动实现

📝本文将创建两个Java文件:MyArraysList.java用于顺序表的实现,Test.java用于顺序表的各个接口的测试

顺序表功能接口概览

import java.util.Arrays;

public class MyArraysList 
    private int[] elem;
    private int usedSize; // 默认值是0
    private static final int DEFAULT_SIZE = 4; // 定义为常量,更加安全

    // 初始化顺序表
    public MyArraysList() 
        this.elem = new int[4];
    
    // 对顺序表进行扩容
    public void expand() 

    //判断当前顺序表是否为空
    public boolean isempty() 

    // 判断当前顺序表是不是满了
    public boolean isFull() 

    // 打印顺序表
    public void display() 


    // 新增元素,默认在数组最后新增
    public void add(int data) 

    // 新增元素,在数组最前面新增
    public void addHead(int data)

    // 在 pos 位置新增元素
    public void addPos(int pos, int data) 


    // 删除表头元素
    public void removeHead() 

    // 删除表尾元素
    public void removeTail() 

    // 指定下标元素的删除
    public void removePos(int pos) 

    //删除第一次出现的关键字key
    public void remove(int toRemove) 


    // 判定是否包含某个元素
    public boolean contains(int toFind)  return true; 

    // 查找某个元素对应的位置
    public int indexOf(int toFind)  return -1; 
    
    // 获取 pos 位置的元素
    public int getPos(int pos)  return -1; 
    
    // 给 pos 位置的元素设为 value
    public void setPos(int pos, int value) 
    
    // 获取顺序表长度
    public int size()  return 0; 
    
    // 清空顺序表
    public void clear() 


基本功能的实现

🌰对顺序表进行扩容

// 对顺序表进行扩容
    public void expand() 
        this.elem = Arrays.copyOf(this.elem, this.usedSize * 2);
        System.out.println("已经成功扩容至原来的两倍"); // 给用户提醒
    

 

🌰判断顺序表是否为空 

     /**
     * 判断当前顺序表是否为空
     * @return true->空的,false->还没空
     */
    public boolean isempty() 
        if (this.usedSize == 0) 
            return true;
        
        else return false;
    

 

🌰判断顺序表是否已满

    /**
     * 判断当前顺序表是不是满了
     * @return true->满了,false->还没满
     */
    public boolean isFull() 
        if (this.usedSize == this.elem.length) return true;
        else return false;
    

🌰打印顺序表

// 打印顺序表
// 打印的第一种方式
    public void display() 
        for (int i = 0; i < this.elem.length; i++) 
            System.out.print(this.elem[i] + " ");
        
        System.out.println();
    
// 打印的第二种方式,用Arrays.toString直接打印
    public void display() 
       System.out.println(Arrays.toString(this.elem));
    

🌰获取顺序表的有效长度

 // 获取顺序表的有效长度
public int size() 
    return this.usedSize;

 🌰清空顺序表

 // 清空顺序表
    public void clear() 
        for (int i = 0; i < this.usedSize; i++) 
            this.elem[i] = 0;
        
        this.usedSize = 0; // 注意有效数组长度也要清零
    

四大功能

一、增加数据 

🌰头插

 // 新增元素,在数组最前面新增
    public void addHead(int data) 
        if (isFull()) 
            System.out.println("数组满了,需要扩容");
            expand();
        
        else 
            // 从usedSize下标开始,不会数组越界(此时的elem.length > usedSize)
            for (int i = this.usedSize; i > 0; --i) 
                this.elem[i] = this.elem[i - 1]; // 从后往前挪动数据,为的是给顺序表的表头腾出来
            
            this.elem[0] = data; // 在顺序表开头插入
            this.usedSize++;    // 数组有效长度加一
        

🌰尾插 

//在数组最后新增
    public void addTail(int data) 
        if (isFull()) 
            System.out.println("数组满了需要扩容");
            expand();
        
        else this.elem[this.usedSize++] = data;
    

🌰指定下标插入 

  1.  判断pos位置是否合法(在顺序表中,数据是连续的,中间不能有空缺)
  2.  判断顺序表是否满了,如果满了,需要扩容
  3.  插入数据(可能需要挪动元素)

// 在 pos 位置新增元素
    public void addPos(int pos, int data) 
        if (pos < 0 || pos > usedSize) 
            System.out.println("pos位置不合法"); return;
        
        if (isFull()) 
            System.out.println("数组满了需要扩容");
            expand();
        
        else 
            // 如果插入位置在顺序表的中间,要注意挪动元素,从后向前挪动,这样不会造成元素值的覆盖
            for (int i = this.usedSize - 1; i >= pos; --i) 
                this.elem[i + 1] = this.elem[i];
            
            this.elem[pos] = data; // 挪动完毕,可以赋值插入
            this.usedSize++;      // 当前元素数加一
        
    


二、删除数据

🌰头删

 // 删除表头元素
    public void removeHead() 
        if (isempty()) 
            System.out.println("顺序表为空,删除不合法");
            return;
        
        // 从第一个元素开始,用后面元素的值覆盖掉前面的值,遍历整个数组就相当于把第一个元素用覆盖的方式抹去了
        for (int i = 1; i < this.usedSize; i++) 
            this.elem[i - 1] = this.elem[i];
        
        this.elem[this.usedSize - 1] = 0; // 现在的最后一个元素是原来的倒数第二个元素, 所以原来的最后一个有效元素要置0
        this.usedSize--; // 不要忘记改变有效数组的长度
    

🌰尾删

    // 删除表尾元素
    public void removeTail() 
        if (isempty()) 
            System.out.println("顺序表为空,删除不合法");
            return;
        
        this.elem[this.usedSize - 1] = 0; // 直接将最后一个元素置0就完成了尾删
        this.usedSize--; // 不要忘记改变有效数组的长度
    

🌰指定下标元素的删除

 // 指定下标元素的删除
    public void removePos(int pos) 
        if (isempty()) 
            System.out.println("顺序表为空,删除不合法");
            return;
        
        if (pos < 0 || pos >= this.usedSize) 
            System.out.println("pos下标不合法");
        
        else 
            for (int i = pos; i < this.usedSize - 1; ++i) 
                this.elem[i] = this.elem[i + 1]; // 从要删除的下标开始,用后边元素的值覆盖掉前面的值,就完成了删除
            
            this.elem[this.usedSize - 1] = 0; // 要完整的删除,将挪动的最后一个元素的原本位置 置空
            this.usedSize--;// 删除后不要忘记更改顺序表的有效长度
        
    

🌰删除首次出现的指定元素

 //删除第一次出现的关键字key
    public void removeKey(int toRemove) 
        if (isempty()) 
            System.out.println("顺序表为空,删除不合法");
            return;
        
        for (int i = 0; i < this.usedSize; i++) 
            if (this.elem[i] == toRemove) 
                // 注意是this.usedSize - 1,将此时 i 之后的元素统一往前搬移一个位置
                for (int j = i; j < this.usedSize - 1; ++j) 
                    this.elem[j] = this.elem[j + 1];
                
                this.elem[this.usedSize - 1] = 0; // 要完整的删除,将挪动的最后一个元素的原本位置 置空
                this.usedSize--; // 删除后不要忘记更改顺序表的有效长度
                return; // 只删除第一次出现的
            
        
    


三、查找数据

🌰获取指定位置的元素

  1. 考虑要获取的位置是否合法
  2. 返回指定位置的元素
 // 获取 pos 位置的元素
    public int getPos(int pos) 
        if (pos < 0 || pos >= this.usedSize)  // 注意这里当pos==this.usedSize也是不合法的,因为此时的pos下标所要获取的是顺序表中第usedSize+1个元素
            System.out.println("pos的位置不合法");
            return -1;
        
        else 
            return this.elem[pos];
        
    

🌰获取指定元素所在的位置

 // 查找某个元素所对应顺序表中的位置
    public int indexOf(int toFind) 
        for (int i = 0; i < this.usedSize; i++) 
            if (this.elem[i] == toFind) 
                return i;
            
        
        System.out.println("在数组中没有找到该元素");
        return -1;
    

🌰查找表中是否包含某个元素 

 /**
 * /判定是否包含某个元素
 * @param toFind 要查找的元素
 * @return true->包含, false->不包含
 */
    public boolean contains(int toFind) 
        for (int i = 0; i < this.usedSize; i++) 
            if (this.elem[i] == toFind)  return true;
        
        return false;
    


四、修改数据

🍑首先考虑修改的位置是否合法

🍑考虑特殊情况

// 给 pos 位置的元素设为 value
    public void setPos(int pos, int value) 
        if (pos < 0 || pos > this.usedSize) 
            System.out.println("pos位置不合法");
            return;
        
        if (pos == this.usedSize)  // 对这种情况要单独处理,此时相等于增加元素
            this.elem[pos] = value;
            this.usedSize++;
        
        else 
            this.elem[pos] = value;
        

    


 总代码

📝MyArraysList.java

import java.util.Arrays;

public class MyArraysList 
    private int[] elem;
    private int usedSize; // 默认值是0
    private static final int DEFAULT_SIZE = 4; // 定义为常量,更加安全
    // 初始化顺序表
    public MyArraysList() 
        this.elem = new int[4];
    
    // 对顺序表进行扩容
    public void expand() 
        this.elem = Arrays.copyOf(this.elem, this.usedSize * 2);
        System.out.println("已经成功扩容至原来的两倍"); // 给用户提醒
    

    /**
     * 判断当前顺序表是否为空
     * @return true->空的,false->还没空
     */
    public boolean isempty() 
        if (this.usedSize == 0) 
            return true;
        
        else return false;
    
    /**
     * 判断当前顺序表是不是满了
     * @return true->满了,false->还没满
     */
    public boolean isFull() 
        if (this.usedSize == this.elem.length) return true;
        else return false;
    
    // 打印顺序表
    public void display() 
        for (int i = 0; i < this.elem.length; i++) 
            System.out.print(this.elem[i] + " ");
        
        // System.out.println(Arrays.toString(this.elem));或者用Arrays.toString打印也行
        System.out.println();
    

    // 新增元素,默认在数组最后新增
    public void addTail(int data) 
        if (isFull()) 
            System.out.println("数组满了需要扩容");
            expand();
        
        else this.elem[this.usedSize++] = data;
    
    // 新增元素,在数组最前面新增
    public void addHead(int data) 
        if (isFull()) 
            System.out.println("数组满了,需要扩容");
            expand();
        
        else 
            // 从usedSize下标开始,不会数组越界(此时的elem.length > usedSize)
            for (int i = this.usedSize; i > 0; --i) 
                this.elem[i] = this.elem[i - 1]; // 从后往前挪动数据,为的是给顺序表的表头腾出来
            
            this.elem[0] = data; // 在顺序表开头插入
            this.usedSize++;    // 数组有效长度加一
        
    
    // 1、判断pos位置是否合法(在顺序表中,数据是连续的,中间不能有空缺)
    // 2、判断顺序表是否满了,如果满了,需要扩容
    // 3、插入数据(可能需要挪作元素)
    // 在 pos 位置新增元素
    public void addPos(int pos, int data) 
        if (pos < 0 || pos > usedSize) 
            System.out.println("pos位置不合法"); return;
        
        if (isFull()) 
            System.out.println("数组满了需要扩容");
            expand();
        
        else 
            // 如果插入位置在顺序表的中间,要注意挪动元素,从后向前挪动,这样不会造成元素值的覆盖
            for (int i = this.usedSize - 1; i >= pos; --i) 
                this.elem[i + 1] = this.elem[i];
            
            this.elem[pos] = data; // 挪动完毕,可以赋值插入
            this.usedSize++;      // 当前元素数加一
        
    

    // 删除表头元素
    public void removeHead() 
        if (isempty()) 
            System.out.println("顺序表为空,删除不合法");
            return;
        
        // 从第一个元素开始,用后面元素的值覆盖掉前面的值,遍历整个数组就相当于把第一个元素用覆盖的方式抹去了
        for (int i = 1; i < this.usedSize; i++) 
            this.elem[i - 1] = this.elem[i];
        
        this.elem[this.usedSize - 1] = 0; // 现在的最后一个元素是原来的倒数第二个元素, 所以原来的最后一个有效元素要置0
        this.usedSize--; // 不要忘记改变有效数组的长度
    
    // 删除表尾元素
    public void removeTail() 
        if (isempty()) 
            System.out.println("顺序表为空,删除不合法");
            return;
        
        this.elem[this.usedSize - 1] = 0; // 直接将最后一个元素置0就完成了尾删
        this.usedSize--; // 不要忘记改变有效数组的长度
    
    // 指定下标元素的删除
    public void removePos(int pos) 
        if (isempty()) 
            System.out.println("顺序表为空,删除不合法");
            return;
        
        if (pos < 0 || pos >= this.usedSize) 
            System.out.println("pos下标不合法");
        
        else 
            for (int i = pos; i < this.usedSize - 1; ++i) 
                this.elem[i] = this.elem[i + 1]; // 从要删除的下标开始,用后边元素的值覆盖掉前面的值,就完成了删除
            
            this.elem[this.usedSize - 1] = 0; // 要完整的删除,将挪动的最后一个元素的原本位置 置空
            this.usedSize--;// 删除后不要忘记更改顺序表的有效长度
        
    
    //删除第一次出现的关键字key
    public void removeKey(int toRemove) 
        if (isempty()) 
            System.out.println("顺序表为空,删除不合法");
            return;
        
        for (int i = 0; i < this.usedSize; i++) 
            if (this.elem[i] == toRemove) 
                // 注意是this.usedSize - 1,将此时 i 之后的元素统一往前搬移一个位置
                for (int j = i; j < this.usedSize - 1; ++j) 
                    this.elem[j] = this.elem[j + 1];
                
                this.elem[this.usedSize - 1] = 0; // 要完整的删除,将挪动的最后一个元素的原本位置 置空
                this.usedSize--; // 删除后不要忘记更改顺序表的有效长度
                return; // 只删除第一次出现的
            
        
    

    /**
     * /判定是否包含某个元素
     * @param toFind 要查找的元素
     * @return true->包含, false->不包含
     */
    public boolean contains(int toFind) 
        for (int i = 0; i < this.usedSize; i++) 
            if (this.elem[i] == toFind)  return true;
        
        return false;
    
    // 查找某个元素对应的位置
    public int indexOf(int toFind) 
        for (int i = 0; i < this.usedSize; i++) 
            if (this.elem[i] == toFind) 
                return i;
            
        
        System.out.println("在数组中没有找到该元素");
        return -1;
    
    // 获取 pos 位置的元素
    public int getPos(int pos) 
        if (pos < 0 || pos >= this.usedSize) 
            System.out.println("pos的位置不合法");
            return -1;
        
        else 
            return this.elem[pos];
        
    
    // 给 pos 位置的元素设为 value
    public void setPos(int pos, int value) 
        if (pos < 0 || pos > this.usedSize) 
            System.out.println("pos位置不合法");
            return;
        
        if (pos == this.usedSize)  // 对这种情况要单独处理,此时相等于增加元素
            this.elem[pos] = value;
            this.usedSize++;
        
        else 
            this.elem[pos] = value;
        

    

    // 获取顺序表长度
    public int size() 
        return this.usedSize;
    
    // 清空顺序表
    public void clear() 
        for (int i = 0; i < this.usedSize; i++) 
            this.elem[i] = 0;
        
        this.usedSize = 0; // 注意有效数组长度也要清零
    



 📝Test.java

🏀对顺序表进行 测试的代码:

/**
 * 对顺序表进行测试的代码
 */
public class Test 
    public static void main(String[] args) 
        MyArraysList myArraysList = new MyArraysList();
        // 尾插四个数字
        myArraysList.addTail(12);
        myArraysList.addTail(32);
        myArraysList.addTail(17);
        myArraysList.addTail(32);
        // 进行扩容
        myArraysList.expand();
        // 指定在pos位置新增元素
        myArraysList.addPos(1, 777);
        // 打印当前的顺序表
        System.out.print("第一次测试的顺序表为:");
        myArraysList.display();

        // 删除顺序表中首次出现的元素32
        myArraysList.removeKey(32);
        // 获取顺序表中 1下标的元素值
        int tmp = myArraysList.getPos(1);
        System.out.println("顺序表中下标为1的元素的值为:" + tmp);
        // 给顺序表中1下标的元素设为value
        myArraysList.setPos(1, 100);
        // 打印此时的顺序表
        System.out.print("修改后第二次测试的顺序表为:"); // 此时1下标的值为100
        myArraysList.display();

        // 顺序表此时的有效长度
        System.out.println("顺序表的有效长度为:" + myArraysList.size());
        // 清空顺序表
        myArraysList.clear();
        System.out.print("第三次测试的顺序表为:");
        myArraysList.display();
        System.out.println("清空顺序表后,顺序表的有效长度为:" + myArraysList.size());

    


🏀测试结果

 好了,今天的文章就到这里了,感谢大家的支持🥰,下篇见😁

数据结构入门顺序表(SeqList)详解(初始化增删查改)

(1)线性表

  • 线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结
    构,常见的线性表:顺序表、链表、栈、队列、字符串…

  • 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。


(2)顺序表

1)什么是顺序表

  • 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组
    上完成数据的增删查改。

  • 顺序表:可动态增长的数组,要求数据是连续存储的

2)顺序表的定义

1、静态顺序表:使用定长数组存储元素

  • 缺陷:给小了不够用,给大了可能浪费,非常不实用
#define N 10
typedef int SLDataType;

typedef struct SeqList
{
	SLDataType array[N];  //定长数组
	size_t size;          //有效数据个数
}SeqList;

2、动态顺序表:使用动态开辟的数组存储元素

  • 动态顺序表可根据我们的需要分配空间大小
  • size 表示当前顺序表中已存放的数据个数
  • capacity 表示顺序表总共能够存放的数据个数
typedef int SLDataType; //类型重命名,后续要存储其它类型时方便更改

typedef struct SeqList
{
	SLDataType* a;    //指向动态开辟的数组
	size_t size;      //有效数据个数
	size_t capacity;  //容量大小
}SeqList;

2)顺序表的接口实现

首先新建一个工程( 博主使用的是 VS2019 )此次用的是动态顺序表

  • SeqList.h(顺序表的类型定义、接口函数声明、引用的头文件)
  • SeqList.c(顺序表接口函数的实现)
  • Test.c(主函数、测试顺序表各个接口功能)

如图:

  • SeqList.h 头文件代码如下:
#pragma once  //防止头文件被二次引用

#include<stdio.h>   /*perror, printf*/
#include<assert.h>  /*assert*/
#include<stdlib.h>  /*realloc*/

typedef int SLDataType;  //后续要存储其它类型时方便更改
//顺序表的动态存储
typedef struct SeqList
{
	SLDataType* a;    //指向动态开辟的数组
	size_t size;      //有效数据个数
	size_t capacity;  //容量大小
}SeqList;

//初始化顺序表
void SeqListInit(SeqList* psl);
//销毁顺序表
void SeqListDestory(SeqList* psl);
//检查顺序表容量是否满了,好进行增容
void CheckCapacity(SeqList* psl);
//顺序表尾插
void SeqListPushBack(SeqList* psl, SLDataType x);//O(1)
//顺序表尾删
void SeqListPopBack(SeqList* psl);//O(1)
//顺序表头插
void SeqListPushFront(SeqList* psl, SLDataType x);//O(n)
//顺序表头删
void SeqListPopFront(SeqList* psl);//O(n)
//打印顺序表
void SeqListPrint(const SeqList* psl);
//在顺序表中查找指定值
int SeqListFind(const SeqList* psl, SLDataType x);
//在顺序表指定下标位置插入数据
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
//在顺序表中删除指定下标位置的数据
void SeqListErase(SeqList* psl, size_t pos);
//查看顺序表中数据个数
size_t SeqListSize(const SeqList* psl);
//修改指定下标位置的数据
void SeqListAt(SeqList* psl, size_t pos, SLDataType x);

这里重点讲解 SeqList.c 中各个接口函数的实现

1、初始化顺序表

记得一定要加上断言,防止传进来的指针为空

void SeqListInit(SeqList* psl)
{
	assert(psl != NULL);  //断言

	psl->a = NULL;  //初始顺序表为空
	psl->size = 0;  //初始数据个数为0
	psl->capacity = 0;  //初始空间容量为0
}

2、销毁(释放)顺序表

记得一定要加上断言,防止传进来的指针为空

void SeqListDestory(SeqList* psl)
{
	assert(psl != NULL);  //断言

	free(psl->a);   //释放动态开辟的空间
	psl->a = NULL;  //置空
	psl->size = 0;  //数据个数置0
	psl->capacity = 0;  //空间容量大小置0
}

3、检查顺序表容量是否满了,好进行增容

为什么不采取插一个数据,增容一个空间的方式呢,因为这样也太麻烦了,代价也太大了,一般情况下,为了避免频繁的增容,当空间满了后,我们不会一个一个的去增,而是一次增容 2 倍,当然也不会一次增容太大,比如 3 倍 4 倍,空间可能会浪费,2 倍是一个折中的选择。

void CheckCapacity(SeqList* psl)
{
	assert(psl != NULL);  //断言

	if (psl->size == psl->capacity)  //检查容量,满了则增容
	{
		size_t newcapacity;  //新容量
		if (psl->capacity == 0)
			newcapacity = psl->capacity = 4;  //原来容量为0,扩容为4
		else
			newcapacity = 2 * psl->capacity;  //原来容量不为0,扩容为原来的2倍
		
		SLDataType* p = (SLDataType*)realloc(psl->a, newcapacity*sizeof(SLDataType));  //扩容
		if (p == NULL)
		{
			perror("realloc");
			exit(-1);
		}
		psl->a = p;  // p 不为空,开辟成功
		psl->capacity = newcapacity;  //更新容量
	}
}

3、顺序表尾插

void SeqListPushBack(SeqList* psl, SLDataType x)
{
	assert(psl != NULL);  //断言

	CheckCapacity(psl);  //检查顺序表容量是否已满

	psl->a[psl->size] = x;  //尾插数据
	psl->size++;  //有效数据个数+1
}
  • 测试其功能

4、顺序表尾删

不知道 SLDataType 是什么类型的数据,不能冒然的将顺序表最后一个数据赋值为 0,我们只需将有效数据个数 size 减 1 即可达到尾删的目的。

void SeqListPopBack(SeqList* psl)
{
	assert(psl != NULL);  //断言
	assert(psl->size > 0);  //顺序表不能为空

	//不知道SLDataType是什么类型的数据,不能冒然的赋值为0
	//psl->a[psl->size - 1] = 0;
	psl->size--;  //有效数据个数-1
}
  • 测试其功能

5、顺序表头插

因为顺序表是连续存储的,所以头插时要依次挪动数据

void SeqListPushFront(SeqList* psl, SLDataType x)
{
	assert(psl);  //断言
	CheckCapacity(psl);  //检查顺序表容量是否已满
    
	int i = 0;
	for (i = psl->size - 1; i >= 0; i--)  //顺序表中[0,size-1]的元素依次向后挪动一位
	{
		psl->a[i + 1] = psl->a[i];
	}
	psl->a[0] = x;  //头插数据
	psl->size++;  //有效数据个数+1
}
  • 测试其功能

6、顺序表头删

因为顺序表是连续存储的,所以头删时要依次挪动数据

void SeqListPopFront(SeqList* psl)
{
	assert(psl);  //断言
	assert(psl->size > 0);  //顺序表不能为空

	int i = 0;
	for (i = 1; i < psl->size; i++)  //顺序表中[1,size-1]的元素依次向前挪动一位
	{
		psl->a[i - 1] = psl->a[i];
	}
	psl->size--;  //有效数据个数-1
}
  • 测试其功能

7、打印顺序表

void SeqListPrint(const SeqList* psl)
{
	assert(psl != NULL);  //断言

	if (psl->size == 0)  //判断顺序表是否为空
	{
		printf("顺序表为空\\n");
		return;
	}

	int i = 0;
	for (i = 0; i < psl->size; i++)  //打印顺序表
	{
		printf("%d ", psl->a[i]);
	}
	printf("\\n");
}

8、在顺序表中查找指定值

int SeqListFind(const SeqList* psl, SLDataType x)
{
	assert(psl);  //断言

	int i = 0;
	for (i = 0; i < psl->size; i++)
	{
		if (psl->a[i] == x)
		{
			return i;  //查找到,返回该值在数组中的下标
		}
	}
	return -1;  //没有查找到
}
  • 测试其功能

9、在顺序表指定下标位置插入数据

将指定位置后的所有数据依次向后挪动一位,为改进代码如下:

int i = 0;
for (i = psl->size - 1; i >= pos; i--)
	psl->a[i + 1] = psl->a[i];
  • 原先这种写法,当顺序表为空 size = 0 时,会导致 i = -1,
    执行 i >= pos 时,i 被算术转换成无符号数,而无符号数的 -1 是一个值很大的正数,
    远大于 pos,满足条件进入循环,会造成越界访问
  • 注:转换并不会改变 i 本身的值,而是执行 i >= pos 时,生成一个临时的值与 pos 比较
  • 如果在顺序表头部插入数据 pos = 0,i 最终也会减减变成 -1,被算术转换后变成一个很大的数
  • 总结:避免负数给到无符号数,或者避免有符号数变成负数后,被算术转换或整型提升后,变成一个很大的数

下面这样写就避免 i 变成 -1 负数了

void SeqListInsert(SeqList* psl, size_t pos, SLDataType x)
{
	assert(psl);  //断言
	assert(pos >= 0 && pos <= psl->size);  //检查pos下标的合法性

	CheckCapacity(psl);  //检查顺序表容量是否已满

	size_t i = 0;
	for (i = psl->size; i > pos; i--)  //将pos位置后面的数据依次向后挪动一位
	{
		psl->a[i] = psl->a[i - 1];
	}
	psl->a[pos] = x;  //插入数据
	psl->size++;  //有效数据个数+1
}
  • 测试其功能
  • 实现了此接口,顺序表头插相当于在下标为 0 位置处插入数据,可以改进下顺序表头插的代码:
void SeqListPushFront(SeqList* psl, SLDataType x)
{
	SeqListInsert(psl, 0, x);  //改造头插接口
}

10、在顺序表中删除指定下标位置的数据

void SeqListErase(SeqList* psl, size_t pos)
{
	assert(psl);  //断言
	assert(psl->size > 0);  //顺序表不能为空
	assert(pos >= 0 && pos < psl->size);  //检查pos下标的合法性

	size_t i = 0;
	for (i = pos + 1; i < psl->size; i++)  //将pos位置后面的数据依次向前挪动一位
	{
		psl->a[i - 1] = psl->a[i];
	}
	psl->size--;  //有效数据个数-1
}
  • 测试其功能
  • 实现了此接口,顺序表头删相当于删除下标为 0 位置处的数据,可以改进下顺序表头删的代码:
//顺序表头删
void SeqListPopFront(SeqList* psl)
{
	SeqListErase(psl, 0);  //改造头删接口
}

11、查看顺序表中有效数据个数

  • 可能大家会有一个疑问,我在主函数里面直接通过定义的结构体变量直接访问就好了呀,为啥还要弄一个函数嘞

  • 在数据结构中有一个约定,如果要访问或修改数据结构中的数据,不要直接去访问,要去调用它的函数来访问和修改,这样更加规范安全,也更方便检查是否出现了越界等一些错误情况

size_t SeqListSize(const SeqList* psl)
{
	assert(psl);  //断言

	return psl->size;
}
  • 补充:

越界不一定报错,系统对越界的检查是一种抽查

  • 越界读一般是检查不出来的

  • 越界写如果是修改到标志位才会检查出来

(系统在数组末尾后设的有标志位,越界写时,恰好修改到标志位了,就会被检查出来)

12、修改指定下标位置的数据

void SeqListAt(SeqList* psl, size_t pos, SLDataType x)
{
	assert(psl);  //断言
	assert(psl->size > 0);  //顺序表不能为空
	assert(pos >= 0 && pos < psl->size);  //检查pos下标的合法性

	psl->a[pos] = x;  //修改pos下标处对应的数据
}

以上是关于《Java数据结构入门》顺序表详解的主要内容,如果未能解决你的问题,请参考以下文章

数据结构入门顺序表(SeqList)详解(初始化增删查改)

数据结构入门顺序表(SeqList)详解(初始化增删查改)

Java 基础选择循环结构语句详解

java中哈希表及其应用详解

Java中顺序表及链表详解

Java集合类——数据结构入门