Java | 数据结构 - 线性表【顺序表+链表】的构建及其基本操作(代码含注释,超详细)

一、什么是线性表?

线性表是由n(n≥0)个数据元素所构成的有限序列,通常表示为(a0,a1,…,ai,…,an-1)。

实现方式:

一种是基于顺序存储的实现——顺序表

另一种是基于链式存储的实现——链表

二、顺序表

1. 顺序表的定义

顺序表,即顺序存储的线性表。顺序存储是用一组地址连续的存储单元依次存放线性表中各个数据元素的存储结构。

2. 顺序表的特点

  • 存储密度高,但需要预先分配“足够应用”的存储空间,这可能会造成存储空间的浪费;其中,存储密度=数据元素本身值所占用的存储空间/该数据元素实际所占用的空间;
  • 便于随机存取;
  • 不便于插入和删除操作,这是因为在顺序表上进行插入和删除操作会引起打量数据元素的移动。
  • 在线性表中逻辑上相邻的数据元素,在物理存储位置上也是相邻的;

3. 顺序表的构建及其基本操作

//顺序表
public class SqList {
	
	private int[] listElem;  //顺序表存储空间
	private int curLen;  //顺序表当前长度
	
	public static void main(String[] args) throws Exception {
		//测试
		SqList sq = new SqList(5);  //构造容量空间为5的顺序表
		sq.display();  //遍历
		System.out.println();
		sq.insert(3, 2);  //插入
		sq.display();  //遍历
		System.out.println();
		System.out.println(sq.indexOf(2));  //查找-按值查下标
		System.out.println(sq.get(3));  //查找-按下标查值
		sq.remove(3);  //删除
		sq.display();  //遍历
		
	}
	//构造方法-构造一个存储空间容量为maxSize的顺序表
	public SqList(int maxSize) {
		curLen = 0;  //置顺序表的当前长度为0
		this.listElem = new int[maxSize];  //为顺序表分配maxSize个存储单元
	}
	
	//1. 将一个已经存在的顺序表置空
	public void clear() {
		curLen = 0;
	}
	
	//2. 判断顺序表是否为空
	public boolean isEmpty() {
		return curLen == 0;
	}
	
	//3. 返回顺序表中的数据元素个数
	public int length() {
		return curLen;
	}

	//4. (查找)返回顺序表中第i个数据元素
	public int get(int i) throws Exception{
		if(i<0 || i>listElem.length-1)
			throw new Exception("第" + i + "个元素不存在");  //不存在则抛出异常
		return listElem[i];
	}
	
	//5. 打印顺序表中的数据元素
	public void display() {
		for(int i=0;i<listElem.length;i++)
			System.out.print(listElem[i]);
	}
	
	//6. (插入)在顺序表的第i个数据元素之前插入一个值为x的数据元素
	public void insert(int i,int x) throws Exception{
		if(curLen == listElem.length)  //判断顺序表是否已满
			throw new Exception("顺序表已满");
		if(i<0 || i>listElem.length)  //判断i是否合法
			throw new Exception("插入位置不合理");
		for(int j=listElem.length-1;j>i;j--)
			listElem[j] = listElem[j-1];  //插入位置及其之后的所有数据元素后移
		listElem[i] = x;  //插入i
		curLen++;  //表长加1	
	}
	
	//7. (删除)删除顺序表中第i个元素
	public void remove(int i) throws Exception{
		if(i<0 || i>listElem.length)  //判断i是否合法
			throw new Exception("删除位置不合理");
		for(int j=i;j<listElem.length-1;j++)
			listElem[j] = listElem[j+1];  //删除位置及其之前的所有数据元素前移
		curLen--;  //表长减1
	}
	
	//8. (查找)返回顺序表中首次出现指定数据元素的位序号,若线性表中不包含此数据元素,则返回-1
	public int indexOf(int x) {
		int index = -1;
		for(int i=0;i<listElem.length;i++) {
			if(x == listElem[i])
				index = i;
		}
		return index;
	}
}

测试结果:

三、链表

1. 链表的定义

链表,即链式存储的线性表。链表中每一个结点包含存放数据元素值的数据域和存放指向逻辑上相邻结点的指针域

单链表:一个结点中只包含一个指针域;

循环链表(环形链表):结构与单链表相似,将单链表的首尾相连,即将单链表的最后一个结点的后继指针指向第一个结点,从未构成一个环状链表;

双向链表:一个结点具有两个指针域,一个指向前驱结点,一个指向后继结点。

2. 链表的特点

  • 采用动态存储分配,不会造成内存浪费和溢出;
  • 执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素。

3. 单链表的构建及其基本操作

//使用泛型构造结点类
public class Node<T> {
	public T data;  //存放结点值
	public Node<T> next;  //后继结点的引用
	//无参时的构造函数
	public Node(){
		this(null,null);
	}
	//带一个参数时的构造函数
	public Node(T data){
		this(data,null);
	}
	//带两个参数时的构造函数
	public Node(T data,Node<T> next)
	{
		this.data=data;
		this.next=next;
	}
}
import java.util.Scanner;
import Node;
//链表
public class LinkList<T>{
	
	public Node<T> head;  //单链表的头指针
	
	public static void main(String[] args) throws Exception{
		//测试
		LinkList<Integer> link = new LinkList<Integer>(5,true); //尾插法
		link.display();  //打印
		System.out.println(link.isEmpty());  //判空
		link.insert(link.length(),7);  //插入
		link.display();  //打印
		System.out.println(link.length());  //长度
		System.out.println(link.get(2));  //查找-按下标查找值
		System.out.println(link.indexOf(7));  //查找-按值查找下标
		link.remove(0);  //删除
		link.display();  //打印
	}
	//构造方法-构造一个带头结点的单链表
	public LinkList() {
		head = new Node<T>();  //初始化头结点	
	}
	//构造方法-构造一个长度为n的单链表
	public LinkList(int n,boolean Order) throws Exception{  
		this();  //调用无参构造方法初始化头结点
		
		if(Order)  //用尾插法顺序建立单链表
			create1(n);
		else
			       //用头插法逆序建立单链表
			create2(n);
	}
	
	//1. 用尾插法顺序建立单链表,其中n为单链表的结点个数
	public void create1(int n) throws Exception{
		Scanner sc = new Scanner(System.in);  //构造用于输入的对象
		for(int j=0;j<n;j++)   //输入n个结点的数据域值
			insert(length(), (T) sc.next());  //生成新结点,插入到表尾
	}
	
	//2. 用头插法逆序建立单链表,其中n为单链表的结点个数
	public void create2(int n) throws Exception{
		Scanner sc = new Scanner(System.in);  //构造用于输入的对象
		for(int j=0;j<n;j++)   //逆序输入n个结点的数据域值
			insert(0,(T) sc.next());  //生成新结点,插入到表头
	}
	
	//3. 将一个已经存在的带头结点单链表置成空表
	public void clear() {
		head.data = null;
		head.next = null;
	}
	
	//4. 判断带头结点的单链表是否为空
	public boolean isEmpty() {
		return head.next == null;
	}
	
	//5. 求带头结点的单链表的长度
	public int length() {
		Node<T> p = head.next;  //指针p指向头结点的下一个结点
		int length = 0;
		while(p != null) {
			p = p.next;  //指针向后移动
			++length;  //长度增加
		}
		return length;  //返回长度
	}
	
	//6. (查找)读取带头结点的单链表中的 第i个结点
	public T get(int i) throws Exception{
		Node<T> p = head.next;  //指针p指向头结点的下一个结点
		int j = 0;  //j为计数器
		while(p!=null && j<i) {  //从首结点开始向后查找,直到p指向第i个结点或p为空
			p = p.next;  //指向后继结点
			++j;  //计数器的值增1
		}
		if(j>i || p==null) {  //i小于0或者大于表长减1时,即i不合法
			throw new Exception("第" + i + "个元素不存在");  //抛出异常
		}
		return p.data;  //返回结点p的数据域值
	}
	
	//7. (查找)在带头结点的单链表中查找值为x的结点
	public int indexOf(T x) {
		Node<T> p = head.next;  //指针p指向头结点的下一个结点
		int j = 0;  //j为计数器
		//下面从单链表中的首结点开始查找,直到p.data为x或到达单链表的表尾
		while(p!=null && !p.data.equals(x)) {
			p = p.next;  //指向下一个结点
			++j;  //计数器的值增1
		}
		if(p!= null)
			return j;  //返回值为x的结点在单链表的位置
		else
			return -1;  //值为x的结点不在单链表中,则返回-1
	}
	
	//8. (插入)在带头结点的单链表中的第i个结点之前插入一个值为x的新结点
	public void insert(int i,T k) throws Exception {
		Node<T> p = head;  //初始化头结点
		int j = -1;  //j为计数器
		while(p!=null && j<i-1) {  //寻找第i个结点的前驱
			p = p.next;  
			++j;  //计数器的值增1
		}
		if(j>i-1 || p==null)  //i不合法
			throw new Exception("插入位置不合法");  //抛出异常
		Node<T> s = new Node<T>(k);  //生成新结点
		s.next = p.next;  //修改链,使新结点插入单链表中
		p.next = s;
	}
	
	//9. (删除)删除带头结点的单链表中的第i个结点
	public void remove(int i) throws Exception{
		Node<T> p = head;  //初始化头结点
		int j = -1;  //j为计数器
		while(p.next!=null && j<i-1) {  //寻找第i个结点的前驱
			p = p.next;  //指针后移
			++j;
		}
		if(j>i-1 || p.next==null) 
			throw new Exception("删除位置不合法");
		p.next = p.next.next;  //修改链指针,使待删除结点从单链表中脱离出来
	}
	
	//10. 输出单链表中的所有结点
	public void display() {
		Node<T> p = head.next;  //指针p指向头结点的下一个结点
		while(p != null) {
			System.out.print(p.data + " ");  //输出结点的值
			p = p.next;  //指针后移
		}
		System.out.println();  //换行
	}
	
}

测试结果:

四、顺序表与链表的比较

  • 链表比较灵活,插入和删除操作的效率较高,但链表的空间利用率较低,适合于视线动态的线性表;
  • 顺序表实现比较简单,因为任何高级程序语言中都有数组类型,并且空间利用率也较高,可高效地进行随机存取,但顺序表不易扩充,插入和删除操作的效率较低,适合于实现相对“稳定”的静态线性表。
  • 3
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java中的线性表可以通过数组或者链表实现。 1.数组实现线性表: 数组是一段连续的内存空间,可以通过下标来访问每一个元素。因此,使用数组实现线性表时,可以将下标作为元素的位置。 定义一个数组来实现线性表: ``` public class ArrayList<T> { private int size; // 线性表的大小 private Object[] elements; // 内部数组 public ArrayList() { this.size = 0; this.elements = new Object[10]; // 初始容量为10 } // 获取元素 public T get(int index) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); } return (T) elements[index]; } // 添加元素 public void add(T element) { if (size == elements.length) { resize(); } elements[size++] = element; } // 删除元素 public T remove(int index) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); } T element = (T) elements[index]; for (int i = index; i < size - 1; i++) { elements[i] = elements[i + 1]; } elements[--size] = null; return element; } // 动态扩容 private void resize() { int newCapacity = elements.length * 2; elements = Arrays.copyOf(elements, newCapacity); } // 获取线性表大小 public int size() { return size; } // 判断线性表是否为空 public boolean isEmpty() { return size == 0; } } ``` 2.链表实现线性表链表是由节点组成的一种数据结构,每个节点包一个元素和一个指向下一个节点的指针。使用链表实现线性表时,可以将节点作为元素,节点的位置则由指针决定。 定义一个链表实现线性表: ``` public class LinkedList<T> { private int size; // 线性表的大小 private Node<T> head; // 头节点 private static class Node<T> { T element; Node<T> next; public Node(T element, Node<T> next) { this.element = element; this.next = next; } } public LinkedList() { this.size = 0; this.head = null; } // 获取元素 public T get(int index) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); } Node<T> node = head; for (int i = 0; i < index; i++) { node = node.next; } return node.element; } // 添加元素 public void add(T element) { Node<T> newNode = new Node<>(element, null); if (head == null) { head = newNode; } else { Node<T> node = head; while (node.next != null) { node = node.next; } node.next = newNode; } size++; } // 删除元素 public T remove(int index) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); } Node<T> node = head; if (index == 0) { head = head.next; } else { for (int i = 0; i < index - 1; i++) { node = node.next; } node.next = node.next.next; } size--; return node.element; } // 获取线性表大小 public int size() { return size; } // 判断线性表是否为空 public boolean isEmpty() { return size == 0; } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

少糖加水

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

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

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

打赏作者

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

抵扣说明:

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

余额充值