Java数据结构——稀疏矩阵、数组模拟队列、单链表、双向链表、单向环形链表


数据结构包括线性结构和非线性结构。

Java LinkedList(链表) 类似于 ArrayList,是一种常用的数据容器。
与 ArrayList 相比,LinkedList 的增加和删除对操作效率更高,而查找和修改的操作效率较低。
以下情况使用 ArrayList :

  • 频繁访问列表中的某一个元素。
  • 只需要在列表末尾进行添加和删除元素操作。

以下情况使用 LinkedList :

  • 你需要通过循环迭代来访问列表中的某些元素。
  • 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。

线性结构:

(1)线性结构作为最常见的数据结构,其特点是数据元素之间存在一对一的线性关系;
(2)线性结构有两种不同的存储结构,即顺序存储结构链式存储结构。顺序存储的线性表称为线性表,顺序表中的存储元素是连续的;
(3)链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素预计相邻元素的地址信息;
(4)线性结构常见的有:数组、队列、链表和栈。

非线性结构:

非线性结构包括:二维数组、多维数组,广义表,树结构,图结构

一、稀疏数组和队列:

1.稀疏数组:

(1)基础知识:

在这里插入图片描述
在这里插入图片描述

(2)代码实现:二维数组与稀疏数组互换

package sparseArray_and_Queue;

public class sparseArrayTest {
	
	public static void main(String[] args) {
		//创建原始二维数组,并赋值
		int array1[][] = new int [5][5];
		array1[1][1] = 1;
		array1[2][2] = 2;
		
		System.out.println("原始二维数组为:");
		//遍历输出二维数组;
		for (int[] is : array1) {
			for(int data : is) {
				System.out.printf("%d\t", data);
			}
			System.out.println();
		}
		
		//遍历二维数组,获取有效元素的个数
		
		int sum = 0;
		for (int i = 0; i < array1.length; i++) {
			for (int j = 0; j < array1.length; j++) {
					if (array1[i][j] != 0) {
						sum++;
					}
			}
		}
		
		//创建稀疏数组,并初始化数组的第一行;
		int sparseArray[][] = new int[sum + 1][3];
		sparseArray[0][0] = 5;
		sparseArray[0][1] = 5;
		sparseArray[0][2] = sum;
		
		//遍历二维数组将非0元素存放到sparseArray中;
		
		int count = 0;
		
		for (int i = 0; i < sparseArray.length; i++) {
			for (int j = 0; j < sparseArray.length; j++) {
				if(array1[i][j] != 0) {
					count++;
					sparseArray[count][0] = i;
					sparseArray[count][1] = j;
					sparseArray[count][2] = array1[i][j];
				}
			}
		}
		
		System.out.println("以下是对应的稀疏数组:");
		// 输出稀疏数组;
		for (int[] is : sparseArray) {
			for (int data : is) {
				System.out.printf("%d\t", data);
			}
			System.out.println();
		}
		
		//恢复:
		int array2[][] = new int [sparseArray[0][0]][sparseArray[0][1]];
		for (int i = 1; i < sparseArray.length; i++) { //注意i从1开始
			array2[sparseArray[i][0]][sparseArray[i][1]] = sparseArray[i][2]; //赋值; 
		}
		//输出恢复的二维数组;
		
		System.out.println("恢复的二维数组为:");
		for (int[] is : array2) {
			for(int data : is) {
				System.out.printf("%d\t", data);
			}
			System.out.println();
		}
		
	}
	
}

2.数组模拟队列(环形队列):

(1)基础知识:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

(2)代码实现:

环形队列的简单案例:

package sparseArray_and_Queue;

import java.awt.Font;
import java.util.Scanner;

import javax.management.RuntimeErrorException;

public class Array_Simulation_Queue {
	
	//进行测试;
		public static void main(String[] args) {
			
			Array_Simulation_Queue queue = new Array_Simulation_Queue(3);
			char key = ' '; //接受用户输入
			Scanner scanner = new Scanner(System.in);
			boolean loop = true;
			//输出一个菜单;
			while(loop) {
				System.out.println("s(show):显示队列");
				System.out.println("e(exit):退出队列");
				System.out.println("a(add):添加数据到队列");
				System.out.println("g(get):从队列取出数据");
				System.out.println("h(head):查看队列头数据");
				key  =  scanner.next().charAt(0);//接收一个字符;
				switch (key) {
				case 's':
					queue.showQueue();
					break;
				case 'a':
					System.out.println("添加一个数:");
					int value = scanner.nextInt();
					queue.addQueue(value);
					break;
				case 'g':
					try {
						int res = queue.getQueue();
						System.out.printf("取出的数据是%d\n", res);
					} catch (Exception e) {
						System.out.println(e.getMessage());
					}
					break;
				case 'h':
					try {
						int res = queue.headQueue();
						System.out.printf("队列头的数据是%d\n",res);
					} catch (Exception e) {
						System.out.println(e.getMessage());
					}
					break;
				case 'e':
					scanner.close();
					loop = false;
					break;
				default:
					break;
				}
			}
			System.out.println("程序退出!");
			
		}
	
	
	
	private int maxSize;
	private int front;
	private int rear;
	private int[] arr;
	
	//创建构造器;
	public Array_Simulation_Queue(int arrayMaxSize){
		this.maxSize = arrayMaxSize;
		this.arr = new int [maxSize];
		this.front = 0;
		this.rear = 0;
	}
	
	//判断队列是否满;
	public boolean isFull() {
		return (rear + 1)%maxSize == front;
	}
	
	//判断队列是否空;
	public boolean isEmpty() {
		return rear == front;
	}
	
	//添加数据到队列中;
	public void addQueue(int data) {
		if(isFull()) {
			System.out.println("队列满,不能添加数据!");
			return;
		}
		// 添加数据,之后后移rear;
		arr[rear] = data;
		rear = (rear + 1) % maxSize;
	}
	
	//出队;
	public int getQueue() {
		if(isEmpty()) {
			throw new RuntimeException("队列空,不能取出数据!");
		}
		int value = arr[front];
		front = (front + 1)%maxSize;
		return value;
	}
	
	//获取当前队列的有效数据个数;
	public int size() {
		return (rear + maxSize - front)%maxSize;
	}
	
	//显示队列数据;
	public void showQueue() {
		if (isEmpty()) {
			System.out.println("队列空!");
			return;
		}
		for (int i = front; i < front + size(); i++) {
			System.out.printf("arr[%d] = %d \n", i % maxSize, arr[i % maxSize]); // 注意取余,防止数组越界
		}
	}
	
	//显示头数据;
	public int headQueue() {
		if (isEmpty()) {
			throw new RuntimeException("队列空!");
		}
		return arr[front];
	}
	
	
}

二、单链表:

(1)基础知识:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

(2)代码实现:

增加:

(1)直接按顺序插入:

package linkedlist;

public class SingleLinkedListDemo {
	public static void main(String[] args) {
		//测试
		//先创建节点;
		HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
		HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
		HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
		HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
		
		//创建链表
		SingleLinkedList singleLinkedList = new SingleLinkedList();
		//加入
		singleLinkedList.add(hero1);
		singleLinkedList.add(hero2);
		singleLinkedList.add(hero3);
		singleLinkedList.add(hero4);
		//显示
		singleLinkedList.showList();
	}
}

class SingleLinkedList{
	//初始化头节点,头节点不能动,不存放具体的数据
	private HeroNode head = new HeroNode(0,"","");
	
	//添加节点到链表中
	//思路:不考虑编号顺序时;
	//1.找到当前链表的最后节点
	//2.将最后这个节点的next 指向新的节点
	public void add(HeroNode heroNode) {
		
		//head节点不能动,因此需要一个中间对象temp 辅助遍历
		HeroNode temp = head;
		
		//遍历链表,找到最后
		while(true) {
			if(temp.next == null) {
				break;
			}
			temp = temp.next;//如果没有找到,将temp后移;
		}
		//当退出while循环时,temp就指向了链表的最后,将最后这个节点的next 指向 新的节点;
		temp.next = heroNode;
	}
	
	//显示链表[遍历]
	public void showList() {
		//先判断链表是否为空
		if(head.next == null) {
			System.out.println("链表为空");
			return;
		}
		//头节点不能懂,需要辅助变量来遍历
		HeroNode temp = head.next;
		while(true) {
			//判断链表是否到最后
			if(temp == null) {
				break;
			}
			System.out.println(temp);//输出节点信息
			temp = temp.next;//一定要将temp后移;不然循环会变成死循环
		}
	}
	
	
}



//定义HeroNode,每个HeroNode对象就是一个 节点
class HeroNode{
	public int no;//编号
	public String name;//名字
	public String nickname;//昵称
	public HeroNode next;//指向下一个节点
	//构造器
	public HeroNode(int no, String name, String nickname) {
		this.no = no;
		this.name = name;
		this.nickname = nickname;
	}
	
	@Override
	public String toString() {
		return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
	}
	
	
	
}

(2) 按照节点顺序插入:
在这里插入图片描述

//第二种添加方式:根据排名将英雄插入到指定位置(如果有这个排名,添加失败并给出提示)
	public void addByOrder(HeroNode heroNode) {
		
		//head节点不能动,因此需要一个中间对象temp 辅助遍历,帮助找到添加的位置
		//单链表,我们找的temp是位于添加位置的前一个节点,否则无法插入
		HeroNode temp = head;
		boolean flag = false;//标识:添加的编号是否存在,默认为false;
		
		while(true) {
			if(temp.next == null) {//说明temp已经在链表的最后
				break;
			}
			if(temp.next.no > heroNode.no) {//位置找到,就在temp的后面插入
				break;
			}
			if(temp.next.no == heroNode.no) {//说明希望添加的heroNode的编号已然存在
				flag = true;
				break;
			}
			temp = temp.next;//后移,遍历链表
		}
		
		if(flag) {
			System.out.printf("%d已经存在不能添加\n", heroNode.no);
		}else {
			heroNode.next = temp.next;
			temp.next = heroNode;
		}
		
	}

在这里插入图片描述

修改:

修改节点信息,根据编号修改,即no编号不能改动。

思路:要修改,那么先根据no找到要修改的那个节点(找到修改,没找到不修改),要找节点就要遍历链表。找到之后修改data域内容。

	//修改节点信息:根据指定的no编号来修改
	public void update(HeroNode newheroNode) {
		if(head.next == null) {
			System.out.println("链表为空,不能修改!");
			return;
		}
		HeroNode temp = head.next;
		boolean flag = false;
		
		while(true) {//使用循环去找该编号对应的节点
			if(temp == null) {
				break;  //表示已经遍历完毕,退出
			}
			if(temp.no == newheroNode.no) {//表示找到该节点
				flag = true;
				break;
			}
			temp = temp.next;
		}
		//根据flag判断是否找到要修改的节点
		if(flag) {
			temp.name = newheroNode.name;
			temp.nickname = newheroNode.nickname;
		}else {//没有找到
			System.out.printf("没有编号为%d的节点,不能修改\n", newheroNode.no);
		}
}

删除节点:

在这里插入图片描述

//删除节点;根据编号no删除
	public void del(int no) {
		HeroNode temp = head;
		boolean flag = false;
		while(true) {//遍历找需要删除的那个节点
			if(temp.next == null) {//链表已到最后
				break;
			}
			if(temp.next.no == no) {//表示找到该节点
				flag = true;
				break;
			}
			temp = temp.next;
		}
		
		if(flag) {//找到,可以删除并进行删除
			temp.next = temp.next.next;
		}else {//没有找到
			System.out.printf("不存在编号为%d的节点\n", no);
		}
	}

(3)单链表常见试题:

在这里插入图片描述
1)求单链表中节点个数:

/**
	 * 获取单链表的节点个数(如果是带头节点的链表,不统计头节点)
	 * head为头节点
	 * 返回的是有效节点的个数
	 */
	public static int getLength(HeroNode head) {
		if(head.next == null) {//空链表
			return 0;
		}
		int length = 0;
		HeroNode cur = head.next;
		while(cur != null) {
			length++;
			cur = cur.next;//遍历
		}
		return length;
	}

2)查找单链表中的倒数第K个节点:

	/*
	 * 查找单链表中的倒数第K个节点:查找意味着遍历,指定了节点意味着多出一个索引值
	 * 1.编写一个方法,接受head节点,同时接受一个索引index
	 * 2.index表示倒数第index个数
	 * 3.先把链表从头到尾遍历,得到链表的总长度
	 * 4.得到size之后,从链表第一个开始遍历到(size-index)个,得到该节点
	 * 5.若找到该节点(注意返回类型),则返回,若没有则返回null。
	 */
	public static HeroNode findIndexNode(HeroNode head, int index) {
		//先判断链表是否为空
		if(head.next == null) {
			return null;//没有找到
		}
		int size = getLength(head);//获取有效节点个数(长度)
		//index校验
		if(index <=0 || index > size) {
			return null;
		}
		//定义辅助变量,进行遍历
		HeroNode cur = head.next;
		for(int i = 0; i < size-index; i++) {//写循环遍历的时候注意去试一下就知道条件咋写了,要分清从哪开始到哪结束
			cur = cur.next;
		}
		return cur;
	}

3)单链表的反转:
思路:

  • 先定义一个节点reverseHead = new HeroNode();
  • 从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端;
  • 原来的链表的 head.next = reverseHead.next。
/*
	 * 单链表的反转:
	 */
	public static void reverseList(HeroNode head) {
		//如果当前链表为空,或者只有一个节点,无需反转,直接返回
		if(head.next == null || head.next.next == null ) {
			return;
		}
		
		//定义辅助变量,遍历原来的链表
		HeroNode cur = head.next;
		HeroNode next = null;//指向当先节点[cur]的下一个节点
		HeroNode reverseHead = new HeroNode(0,"","");
		//遍历原来的链表,每遍历一个节点,就将其取出,并放在新的reverseHead 的最前端
		
		while(cur != null) {
			next = cur.next;//暂时保存当前节点的下一个节点,后续会用到
			cur.next = reverseHead.next;//将cur的下一个节点指向新的链表的最前端
			reverseHead.next = cur;
			cur = next;//将cur后移
		}
		//将head.next指向reverseHead.next,实现单链表的反转
		head.next = reverseHead.next;
	}

4)从尾到头打印单链表:使用栈
在这里插入图片描述

//从尾到头打印单链表:使用栈
	public static void reversePrint(HeroNode head) {
		if(head.next == null) {
			return;
		}
		//创建一个栈,将各个节点压入栈中
		Stack<HeroNode> stack = new Stack<HeroNode>();
		HeroNode cur = head.next;
		while(cur != null) {
			stack.push(cur);
			cur = cur.next;//cur后移,压入下一个节点
		}
		//将栈中的接待你进行打印(即出栈)
		while(stack.size() > 0) {
			System.out.println(stack.pop());
		}
	}

使用递归:

public class Solution {
    ArrayList<Integer> list = new ArrayList();
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        if(listNode!=null){
            printListFromTailToHead(listNode.next);
            list.add(listNode.val);
        }
        return list;
    }
}

三、双向链表:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

四、单向环形链表:

在这里插入图片描述

1.节点的创建及环形单链表的搭建:

在这里插入图片描述

package linkedlist;

public class Josepfu {

	public static void main(String[] args) {
		CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
		circleSingleLinkedList.addBoy(5);
		circleSingleLinkedList.showBoy();	
	}
}

//创建环形单链表
class CircleSingleLinkedList{
	//先创建一个first节点,没有编号
	private Boy first = null;
	
//***********************************************	
	//添加环形,构建环形链表;
	public void addBoy(int nums) {
		if(nums < 1) {
			System.out.println("nums 不正确!");
			return;
		}
		
		//f辅助节点
		Boy curBoy = null;
		//用循环来添加节点(根据编号);
		for (int i = 1; i <= nums; i++) {//编号从1开始
			Boy boy = new Boy(i);
			if (i == 1) {
				first = boy;
				first.setNext(first);//将第一个节点本身构成一个环形;
				curBoy = first;
			}else {
				curBoy.setNext(boy);
				boy.setNext(first);
				curBoy = boy;
			}		
		}
	}
	
//***********************************************	
	//遍历当前 环形单链表;
	public void showBoy() {
		//判断链表是否为空
		if(first == null) {
			System.out.println("链表为空!");
			return;
		}
		
		Boy curBoy = first;
		while(true) {
			System.out.printf("节点的编号为%d \n", curBoy.getNo());
			if(curBoy.getNext() == first) {
				break;
			}
			curBoy = curBoy.getNext(); //后移
		}
	}
}

//创建一个Boy类,表示节点
class Boy{
	private int no;//表示编号
	private Boy next;//指向下一个节点,默认为null;

	public Boy(int no) {
		this.no = no;
	}
	
	public int getNo() {
		return no;
	}
	public void setNo(int no) {
		this.no = no;
	}
	public Boy getNext() {
		return next;
	}
	public void setNext(Boy next) {
		this.next = next;
	}
}

2.约瑟夫问题:节点出圈

在这里插入图片描述

//根据用户的输入计算出节点出圈的顺序;
	/**
	 * startNo 表示从第几个节点开始数
	 * countNum 表示数几次
	 * nums 表示最初有多少个节点在圈中
	 */
	public void countBoy(int startNo, int countNum, int nums) {
		//先对数据进行校验
		if(first == null || startNo < 1 || startNo > nums) {
			System.out.println("输入有误!");
			return;
		}
		//创建辅助指针;
		Boy helper = first;
		//需求此辅助指针,要求他指向链表的最后一个节点;
		while (true) {
			if (helper.getNext() == first) {//满足此条件时,helper已经指向链表最后一个元素
				break;
			}
			helper = helper.getNext();
		}
		//开始报数前,将first移动到开始报数的位子,helper在first之后, startNo-1次;
		for (int i = 0; i < startNo -1; i++) {
			first = first.getNext();
			helper = helper.getNext();
		}
		//开始报数,进行移动,这里是循环的操作,直到圈中只有一个节点
		while(true) {
			if (helper == first) {//此时只有一个节点
				break;
			}
			//出圈的条件:helper和first移动countNum-1次;
			for (int j = 0; j < countNum -1; j++) {
				first = first.getNext();
				helper = helper.getNext();
			}
			//这时first指向的节点,就是要出圈的节点
			System.out.printf("节点%d出圈 \n", first.getNo());
			first = first.getNext();
			helper.setNext(first);
		}
		System.out.printf("最后留在圈中的节点编号为%d \n", first.getNo());
		
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值