文章目录
数据结构包括线性结构和非线性结构。
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());
}