日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

java 二维链表_Java数据结构与算法----数组与链表

發布時間:2024/9/30 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java 二维链表_Java数据结构与算法----数组与链表 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

數據類型

1 數據類型介紹

數據類型的分類(按照結構劃分):線性結構和非線性結構

線性結構:線性結構作為最常用的數據結構,其特點是數據元素之間存在一對一的線性關系

線性結構有兩種不同的存儲結構,即順序存儲結構(數組)和 鏈式存儲結構(鏈表),順序存儲的線性表為順序表,順序表中存儲的元素是連續的

鏈式存儲結構的線性表稱為鏈表,鏈表中的存儲的元素不一定是連續的,元素節點中存放數據元素以及相鄰元素的地址信息

吸納行結構常見的有:數組,隊列,鏈表,棧

非線性結構:

非線性結構包括:二維數組,多維數組,廣義表,樹結構,圖結構

2 數據類型之——稀疏數組

2.1 引入實例

分析問題:因為該二維數組的很多值都是默認值0,因此記錄了很多沒有意義的數據,所以要用到稀疏數組來節省空間

2.2 稀疏數組的基本介紹

當一個數組中大部分元素為0,或者為同一個值的數組時,可以使用稀疏數組來保存該數組

稀疏數組的處理方式是:記錄數組一共有幾行幾列,有多少個不同的值

把具有不同值的元素的行列和值記錄在一個小規模的數組中,從而縮小程序的規模

3

2.3 應用實例

使用稀疏數組來保存類似前面的二維數組(棋盤,地圖等)

把稀疏數組存盤,并且可以重新恢復原來的二維數組

2.4 思路分析

二維數組轉稀疏數組的思路

1) 遍歷原始的二維數組,得到有效數據的個數 sum

2) 根據sum就可以創建稀疏數組 sparsArr int[sum + 1][3]

3) 將二維數組的有效數據存入到稀疏數組

稀疏數組轉原始的二維數組的思路

1) 先讀取稀疏數組的第一行,根據第一行的數據,創建原始的二維數組,比如上面的chessArr = int[11][11]

2) 再讀取稀疏數組后面幾行的內容,并賦給原始的二位數組即可

public class SparseArray {

public static void main(String[] args) {

//創建一個棋盤(原始的二維數組11*11)

//0:表示沒有棋子,1:表示黑色棋子,2:表示藍色棋子

int[][] chessArr = new int[11][11];

chessArr[1][2] = 1;

chessArr[2][3] = 2;

chessArr[4][5] = 2;

//輸出原始的二維數組

System.out.println('原始的二維數組');

for (int[] row : chessArr) {//對二維數組中的整行遍歷

for (int data : row) {

System.out.printf('%d\t', data);

}

System.out.println();

}

//將二維數組轉稀疏數組的思想

//1.先遍歷二維數組,得到非0數據的個數

int sum = 0;

for (int[] row : chessArr) {

for (int data : row) {

if (data != 0) {

sum++;

}

}

}

System.out.println('sum=' + sum);

//創建對應的稀疏數組

int[][] sparsArr = new int[sum + 1][3];

//給稀疏數組賦值

sparsArr[0][0] = chessArr.length;

sparsArr[0][1] = chessArr[0].length;

sparsArr[0][2] = sum;

//遍歷二維數組,將非0的值存放到稀疏數組中

int count = 1;//計數器,用于記錄是第幾個非零數據

for(int i = 0; i

for (int j =0; j

if (chessArr[i][j]!=0){

sparsArr[count][0] = i;

sparsArr[count][1] = j;

sparsArr[count][2] = chessArr[i][j];

count++;

}

}

}

//輸出稀疏數組

System.out.println('稀疏數組');

for (int[]row:sparsArr){

for (int data:row){

System.out.printf('%d\t',data);

}

System.out.println();

}

System.out.println('還原的二維數組');

//將稀疏數組恢復成原始的二維數組

//1.根據sparsArr第一行創建二維數組

int[][] chessArr2 = new int[sparsArr[0][0]][sparsArr[0][1]];

//2.再讀取稀疏數組的后幾行的數據(從第二行開始),并賦值給新的二維數組即可

for (int i = 1; i<=sparsArr[0][2];i++){

chessArr2[sparsArr[i][0]][sparsArr[i][1]] = sparsArr[i][2];

}

for (int[] row:chessArr2){

for (int data:row){

System.out.printf('%d\t',data);

}

System.out.println();

}

}}

3 單向隊列

3.1 隊列介紹隊列是一個有序列表,可以用數組

或是鏈表

來實現

遵循先入先出

的原則,即:先存入隊列的數據要先取出,后存入的要后取出

示意圖(用數組模擬隊列)

3.2 數組模擬隊列隊列本身是有序列表,若使用數組的結構來存儲隊列的數據,則隊列數組的聲明如上圖所示,其中maxSize是該隊列的最大容量

因為隊列的輸出、輸入是分別從前后端來處理的,因此需要兩個變量front及rear分別記錄隊列前后端的下標,front會隨著數據輸出而改變,rear會隨著數據的插入而改變

3.3 思路分析

我們將數據存入隊列的時候稱為“addQueue”,addQueue的處理需要有兩個步驟將尾指針(rear)往后移: rear + 1 (隊列為空時:rear == front)

若為指針rear小于隊列的最大下標,maxSize-1, 則將數據存入rear所指的數組元素中,否則無法存入數據。(隊列滿時:rear == maxSize-1)

3.4 代碼實現import?java.util.Scanner;public?class?ArrayQueueDemo?{

public?static?void?main(String[]?args)?{

ArrayQueue?arrayQueue?=?new?ArrayQueue(5);

Scanner?s?=?new?Scanner(System.in);

boolean?loop?=?true;

while?(loop){

System.out.println('a(add):添加數據');

System.out.println('g(get):得到數據');

System.out.println('s(show):顯示數據');

System.out.println('q(quit):退出程序');

char?text?=?s.next().charAt(0);

switch?(text){

case?'a':

System.out.println('請輸入要添加的數據');

arrayQueue.addQueue(s.nextInt());

break;

case?'g':

try{

System.out.println('得到的數據為');

arrayQueue.getQueue();

}catch?(Exception?e){

System.out.println(e.getMessage());

}

break;

case?'s':

arrayQueue.show();

break;

case?'q':

s.close();//關閉輸入器防止異常

loop?=?false;

break;

}

}

}}//創建數組隊列類class?ArrayQueue{

private?int?maxSize;//表示數組的最大容量

private?int?front;//頭指針

private?int?rear;//尾指針

private?int[]?arr;//該數組用于存放數據,模擬隊列

//創建數組隊列的構造器

public?ArrayQueue(int?maxSize){

this.maxSize?=?maxSize;

this.arr?=?new?int[maxSize];

front?=?-1;//指向隊列頭部,分析出front是指向隊列頭的前一個位置

rear?=?-1;//指向隊列尾部,指向隊列尾(即隊列最后一個數據)

}

//判斷隊列是否為空

public?boolean?isEmpty(){

return?front?==?rear;

}

//判斷隊列是否滿了

public?boolean?isFull(){

return?rear?==?maxSize?-?1;

}

//向隊列中添加數據

public?void?addQueue(int?n){

//如果滿了則輸出異常不添加數據

if?(isFull()){

System.out.println('錯誤:隊列已滿不能再添加');

return;

}

//沒滿,則指針后移,向指針所指的格子中添加數據

rear++;

arr[rear]?=?n;

}

//從隊列中獲得數據

public?int?getQueue(){

//如果隊列是空的,拋出異常

if?(isEmpty()){

throw?new?RuntimeException('隊列是空的,不能取出數據');

}

//指針后移返回數據

front++;

return?arr[front];

}

//將隊列格式化輸出

public?void?show(){

if?(isEmpty()){

System.out.println('隊列是空的,沒有數據');

return;

}

for?(int?i?=?0;?i

System.out.printf('arr[%d]?=?%d\n',i,arr[i]);

}

}}

3.5 存在的問題目前數組使用一次就不能繼續用了,沒有達到復用的效果,造成了空間浪費

將這個數組使用算法,改進成一個環形隊列(用取模的方式%)

(類比鐘表:maxSize為12)

4 數組模擬隊列的改進——環形隊列

4.1 思路分析對front變量的含義做一個調整:front就指向隊列的第一個元素,也就是說arr[front] 就是隊列的第一個元素front的初始值為0

對rear變量的含義做一個調整:rear指向隊列的最后一個元素的后一個位置,因為希望空出一個空間(rear指向的空間

)做約定(該約定用來判斷隊列是不是滿了)rear 的初始值是0

當隊列滿時,條件為 (rear + 1)%maxSize == front【滿了】

(該條算法是在判斷rear節點的下一個節點是不是front,如果是,則這個環形隊列滿了)

隊列為空的條件是:rear == front【空的】

當我們這樣分析,隊列中的有效的數據個數(rear+maxSize-front)%maxSize

(類似于鐘表,5點和16點在表盤上差了幾點:(5+12-16)%12 = 1)環形隊列示意圖

4.2 注意約定的位置(內容永遠為空的位置,最后一個數據位的下一個位置) 是在動態變化的,永遠位于最后一個元素的后一個位置

(注意:環形隊列是放不滿的(在當前算法下),一定會有一個空余的位置)

addQueue時和單向隊列的區別

1)單向隊列:先后移,再插入數據(rear指向的位置永遠是有數據的)

2)環形隊列:先插入數據,再后移(rear指向的位置永遠是空的)

getQueue時和單向隊列的區別:

1)單向隊列:front++,返回arr[front](front的初始值是-1)

2)環形隊列:返回arr[front],front++(front的初始值是0)

show()和單向隊列的區別

1)單向隊列:從i=0,遍歷到i=arr.length()(將數據全部打印出)

2)環形隊列:從i=front,遍歷到 i=front+size,(左閉右開)

(其中size = (rear + maxSize -front)%maxSize)

isFull和單向隊列的區別:

1)單向隊列:rear == masSize-1

2)環形隊列:(rear+1)%maxSize ==front

(rear的下一個元素是front,則說明該隊列滿了)環形隊列滿的條件

環形隊列空的條件

4.3 代碼實現

import java.util.Scanner;public class CircleArrayQueueDemo {

public static void main(String[] args) {

CircleArrayQueue circleArrayQueue = new CircleArrayQueue(5);//說明:設置4,但是有效數據最大是3,有一個空間是作為約定的

Scanner s = new Scanner(System.in);

boolean loop = true;

while (loop) {

System.out.println('a(add):添加數據');

System.out.println('g(get):得到數據');

System.out.println('s(show):顯示數據');

System.out.println('q(quit):退出程序');

System.out.println('h(head):查看隊列的第一個數據');

char text = s.next().charAt(0);

switch (text) {

case 'a':

System.out.println('請輸入要添加的數據');

circleArrayQueue.addQueue(s.nextInt());

break;

case 'g':

try {

System.out.println('得到的數據為' + circleArrayQueue.getQueue());

} catch (Exception e) {

System.out.println(e.getMessage());

}

break;

case 's':

circleArrayQueue.show();

break;

case 'q':

s.close();//關閉輸入器防止異常

loop = false;

break;

case 'h':

circleArrayQueue.headQueue();

break;

}

}

}}//創建數組隊列類class CircleArrayQueue {

private int maxSize;//表示數組的最大容量

private int front;//頭指針

private int rear;//尾指針

private int[] arr;//該數組用于存放數據,模擬隊列

//創建數組隊列的構造器

public CircleArrayQueue(int maxSize) {

this.maxSize = maxSize;

this.arr = new int[maxSize];

front = 0;//指向隊列頭部,即隊列的第一個元素

rear = 0;//指向隊列尾部,指向隊列尾的后一個數據

}

//判斷隊列是否為空

public boolean isEmpty() {

return front == rear;

}

//判斷隊列是否滿了

public boolean isFull() {

//如果尾指針的下一個元素是front,則滿了

return (rear + 1) % maxSize == front;

}

//向隊列中添加數據

public void addQueue(int n) {

//如果滿了則輸出異常不添加數據

if (isFull()) {

System.out.println('錯誤:隊列已滿不能再添加');

return;

}

//沒滿,向當前位置所指的格子中添加數據(因為rear所指的格子是空的)

arr[rear] = n;

//指針后移

rear = (rear + 1) % maxSize;//防止出界

}

//從隊列中獲得數據

public int getQueue() {

//如果隊列是空的,拋出異常

if (isEmpty()) {

throw new RuntimeException('隊列是空的,不能取出數據');

}

//這里需要分析出front是指向隊列的第一個元素

//先輸出,再后移front

//1. 先把front對應的值保留到一個臨時變量

//2. 將front后移

//3. 將臨時變量返回

int temp = arr[front];

front = (front + 1) % maxSize;

return temp;

}

//將隊列格式化輸出

public void show() {

if (isEmpty()) {

System.out.println('隊列是空的,沒有數據');

return;

}

//思路:從front開始遍歷,遍歷多少個元素

for (int i = front; i < front + (rear - front + maxSize) % maxSize; i++) {

System.out.printf('arr[%d] = %d\n', i % maxSize, arr[i % maxSize]);

}

}

//顯示隊列的頭數據,不是取出數據

public int headQueue() {

if (isEmpty()) {

throw new RuntimeException('隊列是空的,沒有數據');

}

return arr[front];

}}

5 單鏈表

5.1 單鏈表介紹

鏈表是有序的列表,但是它在內存中的存儲如下

(實際在內存中的存儲)

邏輯結構示意圖如下

總結鏈表是以結點的方式來存儲的,鏈式存儲

每個節點包含data域,next域:指向下一個節點

如圖:發現在內存中鏈表的各個節點不一定是有序存儲的

鏈表分帶頭節點和不帶頭結點的鏈表,根據實際需求來確定

5.2 鏈表的創建按順序直接在尾部添加

每個節點中的內容

添加(創建)的過程

1)先創建一個head頭節點,作用是表示單鏈表的頭(標明此鏈表的頭部位置)

2)后面我們每添加一個節點,就直接加入到鏈表的最后

3)遍歷:通過一個輔助節點遍歷,幫助遍歷整個鏈表

按照編號順序添加

根據排名將英雄插入到指定的位置,(如果該位置已經存在英雄,則添加失敗)

思路:

1)首先找到新添加的節點位置,是通過輔助變量(指針)找到的,通過遍歷得到

2)新的節點.next = temp.next

3)temp.next = 新的節點

5.3 節點的修改

通過輔助(指針)遍歷鏈表,發現節點內部編號相等,則與新的節點內容進行互換

5.4 節點的刪除

思路:我們先找到需要刪除的節點的前一個結點

temp.next = temp.next.next

(不用考慮被刪除的節點是最后一個的情況,temp.next = null和temp.next.next 在這種情況下是一樣的)

被刪除的節點,將不會有其他的引用所指向,會被GC回收

5.5 代碼實現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.addByOrder(hero1);

singleLinkedList.addByOrder(hero2);

singleLinkedList.addByOrder(hero2);

singleLinkedList.add(hero3);

singleLinkedList.add(hero4);

singleLinkedList.List();

singleLinkedList.delete(hero4);

singleLinkedList.delete(hero3);

singleLinkedList.delete(hero2);

singleLinkedList.delete(hero1);

System.out.println('刪除后');

singleLinkedList.List();

}}class?SingleLinkedList?{

//先初始化一個頭結點,頭節點不要動,不要存放具體的數值

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?=?temp.next;

}

//while退出時,temp已經指向的最后的節點

temp.next?=?heroNode;

}

public?void?addByOrder(HeroNode?heroNode)?{

//因為頭節點不能動,因此我們仍然通過一個輔助指針(變量)來幫助找到添加的位置

//因為單鏈表,因此我們找到的temp是位于添加位置的前一個結點,否則插入不進去

HeroNode?temp?=?head;//輔助指針,初始值在head

boolean?flag?=?false;//標識:表示該插入的對象是否已經在鏈表中存在了,默認為false(沒有存在)

//遍歷,從head開始到鏈表尾

while?(true)?{

//如果已經在鏈表尾

if?(temp.next?==?null)?{

break;

}

//如果找到位置,就在temp后面插入

if?(temp.next.heroNo?>?heroNode.heroNo)?{//temp的heroNo不大于,但是temp.next大于,說明插入位置在temp和temp.next之間

break;//找到位置,退出循環

}

if?(temp.next.heroNo?==?heroNode.heroNo)?{

flag?=?true;//說明該節點存在

break;

}

//如果以上條件都不滿足,將temp后移

temp?=?temp.next;

}

//此時得到了一個flag值或者一個temp位置

//首先判斷flag

if?(flag)?{

//說明該節點已經存在

System.out.printf('節點%d已經存在,不能再添加\n',?heroNode.heroNo);

}?else?{

//該節點不存在的話,就在temp后面插入新的節點

heroNode.next?=?temp.next;

temp.next?=?heroNode;

}

}

//修改節點

public?void?update(HeroNode?newHeroNode)?{

//首先確定一下鏈表是否為空

if?(head.next?==?null)?{

System.out.println('鏈表為空,無法修改');

}

//創建輔助結點來遍歷鏈表

HeroNode?temp?=?head.next;

//創建flag變量,判斷該節點是否找到

boolean?flag?=?false;

while?(true)?{

//如果已經遍歷到尾節點,終止

if?(temp?==?null)?{

break;

}

if?(temp.heroNo?==?newHeroNode.heroNo)?{

//找到該節點

flag?=?true;

break;

}?else?{

temp?=?temp.next;

}

}

//循環結束后通過flag值判斷是否修改

if?(flag)?{

temp.heroName?=?newHeroNode.heroName;

temp.nickName?=?newHeroNode.nickName;

//next和no都不用變

}?else?{

System.out.printf('沒有編號為%d的節點,無法修改\n',?newHeroNode.heroNo);

}

}

//刪除節點

//思路:

//1.head不能動,因此我們需要一個temp輔助節點找到待刪除節點的前一個節點

//2.說明我們在比較時,是temp.next.heroNo?和要刪除的節點的heroNo比較

public?void?delete(HeroNode?delHeroNode)?{

//如果鏈表為空,無法刪除

if?(head.next?==?null)?{

System.out.println('鏈表為空,無法刪除\n');

return;

}

//構建輔助節點,幫忙遍歷鏈表

HeroNode?temp?=?head;

//flag表示是否找到該節點的前一位,默認為false

boolean?flag?=?false;

while?(true)?{

//如果遍歷到最后一位,說明該節點不存在,終止循環

if?(temp.next?==?null)?{

break;

}

if?(temp.next.heroNo?==?delHeroNode.heroNo)?{

//找到了要刪除的節點的上一位

flag?=?true;

break;

}?else?{

//沒有找到,繼續往后走

temp?=?temp.next;

}

}

//循環終止后通過判斷flag值決定是否刪除節點

if?(flag)?{

//如果要刪除的節點在最后一位,則將上一位的next指向null

/*if?(temp.next.next==null){

temp.next?=?null;

}

else?{

//要刪除的節點不在最后一位

temp.next?=?temp.next.next;

}*/

//如果要刪除的節點在最后一位,則temp.next.next本身就等于null,以上兩種情況可以合并

temp.next?=?temp.next.next;

}?else?{

System.out.printf('沒有編號為%d的節點,無法刪除\n',?delHeroNode.heroNo);

}

}

public?void?List()?{

//判斷鏈表是否為空

if?(head.next?==?null)?{

System.out.println('鏈表為空');

return;

}

//因為頭節點不能動,所以需要一個輔助節點來遍歷

HeroNode?temp?=?head.next;

while?(true)?{

//判斷是否到鏈表最后

if?(temp?==?null)?{

break;

}

//如果還沒有遍歷到最后

System.out.println(temp);

//將temp后移

temp?=?temp.next;

}

}}//定義HeroNode,每一個HeroNode對象就是一個節點class?HeroNode?{

public?int?heroNo;

public?String?heroName;

public?String?nickName;

HeroNode?next;

public?HeroNode(int?heroNo,?String?heroName,?String?nickName)?{

this.heroNo?=?heroNo;

this.heroName?=?heroName;

this.nickName?=?nickName;

}

//為了顯示方法,我們重寫toString

public?String?toString()?{

return?'HeroNode?[no?=?'?+?heroNo?+?',?name?=?'?+?heroName?+?',?nickname?=?'?+?nickName?+?']';

}}

5.6 單鏈表面試題

5.6.1 求單鏈表中有效節點的個數

韓老師的方法寫在了測試類中,為靜態方法:

/**

*

* @param head 鏈表的頭節點

* @return 返回鏈表的長度

*/

public static int getLength(HeroNode head){

//空鏈表

if (head.next == null){

return 0;

}

int result = 0;

//輔助接點遍歷,將頭節點排除在外

HeroNode cur = head.next;

while (cur!=null){

result++;

cur = cur.next;

}

return result;

}//同時因為head為SingleLinkedList類中的private屬性,所以在SingleLinkedList中添加一個getter方法

public HeroNode getHead() {

return head;

}

//==========================

我寫在了SingleLinkedList類中,為實例方法,不需要參數,可以直接使用對象調用//方法:獲取單鏈表的節點的個數(如果是帶頭節點的,需要不統計頭節點)

public?int?getLength2(){

int?result?=?0;

HeroNode?temp?=?head;

while?(true){

if?(temp.next?==?null){

break;

}

temp?=?temp.next;

result++;

}

return?result;

}

我感覺在實際情況中,韓老師的方法更對一些,因為SingleLinkedList在實際應用情況下是封裝好的,外部是不能夠更改內部的,所以應該用外部的方法取實現要求。

5.6.2 查找單鏈表中的倒數第k個節點

思路:編寫一個方法,接受head節點,同時接受一個index

index表示是倒數第index個節點

先把鏈表從頭到尾遍歷,得到鏈表的總長度getLength

得到size之后,我們從鏈表的第一個節點(非head)開始遍歷(size-index)個,就可以得到

如果找到了,則返回該節點,否則返回null示意圖

代碼示例:

/**

*

* @param head 鏈表的頭節點

* @param k

* @return 返回倒數第K個節點

*/

public static HeroNode findLastKNode(HeroNode head, int k){

//如果鏈表為空,返回null

if (head.next == null){

return null;

}

//找到鏈表的長度

int length = getLength(head);

//先做一個index校驗

if ( k <= 0 || k > length){

return null;

}

//輔助節點幫助遍歷

HeroNode cur = head.next;

//遍歷K次

for (int i = 0; i < length - k; i++) {

cur = cur.next;

}

return cur;

}

5.6.3 從尾到頭打印單鏈表(反向遍歷)

思路:方式1:先將單鏈表進行反轉操作,然后再遍歷即可,但是這樣做的問題會破壞原來的單鏈表的結構,不建議

方式2:利用棧數據結構,將各個節點壓入到棧中,利用棧的先進后出的特點實現了逆序打印的效果

總結

以上是生活随笔為你收集整理的java 二维链表_Java数据结构与算法----数组与链表的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。