順序表結構的存儲方式非常容易理解,操作也十分方便,但是順序結構有如下缺點:
1.在插入或刪除時,往往需要移動大量數據
2.如果表比較大,有時難以分配比較足夠連續的存儲空間,往往導致內存分配失敗.
為了克服上述順序表的缺點,可以采用鏈表結構,鏈表結構是一種動態存儲分配的結構形式,可以根據需要動態申請所需的內存單元.
什么是鏈表結構
典型的鏈表結構,鏈表中每一個結點都應包括兩部分
- 數據部分,保存當前結點的實際數據
- 地址部分,保存的是下一個結點的引用
鏈表結構就是由許多這種結點構成的,在進行鏈表操作時,首先需要定義一個"頭引用"變量,一般以(head)表示,該引用變量指向鏈表結構的第一個結點,第一個結點的地址部分又之向第二個結點,第二個結點的地址部分又指向第三個結點,一直到最后一個結點,這時最后一個結點的地址部分為空,稱為"表尾",一般在表層的地址部分放一個空地址null,鏈表到此結束,如下圖
由于采用了引用來指示下一個數據的地址,因此在鏈表結構中,邏輯上相鄰的結點在內存中并不一定相鄰,邏輯相鄰關系通過地址部分的引用變量來實現.
鏈表結構帶來的最大好處就是結點之間不要求連續存放,因此在保存大量數據時,不需要分配一塊連續的存儲空間,用戶可以用new函數來動態分配結點的存儲空間,當刪除某個結點時,給該結點賦值null,釋放其占用的內存空間.
當然鏈表結構也有缺點,就是浪費存儲空間,因此,對于每個結點數據,都要額外保存一個引用變量,但是,在某些場合,鏈表結構所帶來的好處還是大于其缺點的.
對于鏈表結構的訪問只能從表頭按個查找,即從head的頭結點,到第二個結點,然后第三個結點,直到找到合適的結點.
鏈式存儲是最常用的存儲方式之一,它不僅可以用來表示線性表,而且還可以用來表示各種非線性的數據結構,鏈表結構有如下幾種
- 單鏈表:同上面的鏈式結構一樣,每個結點中只包含一個引用.
- 雙向鏈表:若每個結點包含兩個引用,一個是頭引用,一個是尾引用,一個指向上一個結點,一個指向下一個結點.
- 單循環鏈表:在單鏈表中,將終端結點的飲用域null改為指向表頭結點或開始結點即可構成單循環鏈表.
- 多重鏈的循環鏈表:如果將表中結點鏈在多個環上,將構成多重鏈的循環語法.
接下來,將分析如何在java語言中建立鏈表,并完成鏈表結構的基本運算.
準備數據
下面我們鏈表結構的程序設計,首先需要準備數據,也就是準備在鏈表操作中需要用到的變量及類等
class DATA2 //結點數據
{
String key;
String name;
int age;
}
class CLType
{
DATA2 nodeData = new DATA2(); //保存當前結點的數據
CLType nextNode;
}
上面的代碼定義了結點的數據DATA2,以及鏈表結構的類CLType,結點的具體數據保存在DATA2中,下一個結點的引用保存在nextNode中
追加結點
追加結點即在鏈表末尾增加一個結點,表尾結點的部分保存原來是null,此時需要將其設置為新增結點的地址(即原表尾結點指向新增結點),然后將新增結點的地址部分設置為null,即新增結點成為表尾.
由于一般情況下,鏈表只有一個頭引用head,要在末尾增加結點就需要從頭引用head 開始按個檢查,直到找到最后一個結點.
典型的追加結點步驟如下:
- 追加一個結點
- 1.首先分配內存空間,保存新增的結點
- 2.從頭引用開始檢查,直到找到最后一個結點,
- 3.將結尾結點的內存地址設為新的結點
- 4.將新的結點的地址部分設置為空地址,null,即新結點成為表尾.
public CLType CLAddEnd(CLType head,DATA2 nodeData){
//1.分配內存,保存新增的結點數據
CLType node,htemp;
if((node=new CLType())==null){
System.out.println("申請內存失敗! \n");
return null;
}else{
node.nextData = nodeData; //2.保存數據
node.nextNode = null; //3.設置下一個結點的索引為null,因為追加的這個是鏈尾
if(head ==null){ //4.判斷鏈頭是否為空,如果為空,則直接賦值并返回
head = node;
return head;
}
htemp = head;
while(htemp.nextNode !=null){//5.循環判斷是否是鏈尾,如果不是鏈尾則繼續判斷
htemp = htemp.nextNode;
}
htemp.nextNode = node; //6.判斷完成后這個肯定是鏈尾了,直接賦值
return head;
}
}
上述代碼中,輸入參數head為鏈表頭引用,輸入參數nodeData為結點保存的數據,程序中,使用new關鍵字申請保存結點數據的內存空間,如果分配內存成功,node中將保存指向內存區域的引用,然后,將傳入的nodeData保存到申請的內存區域,并設置該結點指向下一個結點的引用值為null.
插入頭結點
插入頭結點也就是在鏈表首部添加結點的過程,有如下幾個步驟:
- 分配內存空間,保存新增的結點.
- 使新增結點指向頭引用head所指向的結點
- 使頭引用head指向新增結點.
public CLType CLAddFirst(CLType head,DATA2 nodeData){
CLType node;
if((node =new CLType())==null){
System.out.println("申請內存失敗! \n");
return null;
}else{
node.nextData = nodeData;
node.nextNode = head;
head = node;
return head;
}
}
查找結點
查找結點就是在鏈表結構中查找需要的元素.
public CLType CLFindNode(CLType head,String findkey){
CLType htemp;
htemp = head;
while(htemp.nextNode!=null){
if(htemp.nextData.key.compareTo(findkey)==0){
return htemp;
}
htemp = htemp.nextNode;
}
return null;
}
插入結點
插入結點就是在鏈表中間部分的指定位置增加一個結點,插入結點的過程如下:
- 分配內存空間,保存新增的結點
- 找到需要插入的邏輯位置,也就是位于哪兩個結點之間
- 修改插入結點位置的引用,將新增結點的引用指向找到插入結點位置的飲用
- 將新增結點的位置引用指向插入結點原來的地址
public CLType CLInsertNode(CLType head,String findkey,DATA2 nodeData){
CLType node,nodetemp;
if((node=new CLType())==null){
System.out.println("申請內存失敗! \n");
return null;
}
node.nextData = nodeData;
nodetemp = head.CLFindNode(head, findkey);
if(nodetemp!=null){
node.nextNode = nodetemp.nextNode;
nodetemp.nextNode = node;
}else{
System.out.println("插入失敗 \n");
}
return head;
}
刪除結點
刪除結點就是刪除鏈表結構中的結點引用,步驟如下:
- 查找需要刪除的結點
- 使前一結點指向當前結點的下一個結點
- 刪除結點
public int CLDeleteNode(CLType head,String key){
CLType node,htemp; //保存上一個結點和當前結點
htemp = head; //保存當前結點
node = head;
while(htemp!=null){
if(htemp.nextData.key.compareTo(key)==0){
node.nextNode = htemp.nextNode;
htemp = null;
return 1;
}else{
node = htemp;
htemp = htemp.nextNode;
}
}
return 0;
}
計算鏈表長度
計算鏈表長度即統計鏈表結構中結點的數量,在順序表中比較方便,在鏈表結構中需要遍歷整個鏈表結構來計算長度.
public int CLLength(CLType head){
int length = 0;
CLType htemp = head;
while(htemp!=null){
length++;
htemp = htemp.nextNode;
}
return length;
}
完整實現代碼如下:
package LinkedList;
/**
* 數據結點類型
* 定義鏈表結構
* @author feiyu
*
*/
public class CLType {
DATA2 nextData = new DATA2(); //當前結點的數據類型
CLType nextNode; //儲存下一個結點的位置
/**
* 追加一個結點
* 1.首先分配內存空間,保存新增的結點
* 2.從頭引用開始檢查,直到找到最后一個結點,
* 3.將結尾結點的內存地址設為新的結點
* 4.將新的結點的地址部分設置為空地址,null,即新結點成為表尾.
* @param head 頭結點
* @param nodeData 結點數據
* @return 返回頭結點
*/
public CLType CLAddEnd(CLType head,DATA2 nodeData){
//1.分配內存,保存新增的結點數據
CLType node,htemp;
if((node=new CLType())==null){
System.out.println("申請內存失敗! \n");
return null;
}else{
node.nextData = nodeData; //2.保存數據
node.nextNode = null; //3.設置下一個結點的索引為null,因為追加的這個是鏈尾
if(head ==null){ //4.判斷鏈頭是否為空,如果為空,則直接賦值并返回
head = node;
return head;
}
htemp = head;
while(htemp.nextNode !=null){//5.循環判斷是否是鏈尾,如果不是鏈尾則繼續判斷
htemp = htemp.nextNode;
}
htemp.nextNode = node; //6.判斷完成后這個肯定是鏈尾了,直接賦值
return head;
}
}
/**
* 插入頭結點
* 1.分配內存空間,保存新增的結點
* 2.將新增結點指向頭引用的head所指向的結點
* 3.使頭引用指向新增的結點 ,有點繞,就是交換了一下位置,讓head原來的頭結點指向新的頭結點
* @param head
* @param nodeData
* @return
*/
public CLType CLAddFirst(CLType head,DATA2 nodeData){
CLType node;
if((node =new CLType())==null){
System.out.println("申請內存失敗! \n");
return null;
}else{
node.nextData = nodeData;
node.nextNode = head;
head = node;
return head;
}
}
/**
* 查找結點
* @param head 頭結點
* @param findkey
* @return
*/
public CLType CLFindNode(CLType head,String findkey){
CLType htemp;
htemp = head;
while(htemp.nextNode!=null){
if(htemp.nextData.key.compareTo(findkey)==0){
return htemp;
}
htemp = htemp.nextNode;
}
return null;
}
/**
* 插入結點
* 1.分配內存空間,保存新增的結點
* 2.查找關鍵字,找到需要插入的結點位置并返回
* 3.把找到的結點位置地址保存到新的結點地址位置
* 4.把找到的結點位置指向新的結點
* @param head
* @param findkey
* @param nodeData
* @return
*/
public CLType CLInsertNode(CLType head,String findkey,DATA2 nodeData){
CLType node,nodetemp;
if((node=new CLType())==null){
System.out.println("申請內存失敗! \n");
return null;
}
node.nextData = nodeData;
nodetemp = head.CLFindNode(head, findkey);
if(nodetemp!=null){
node.nextNode = nodetemp.nextNode;
nodetemp.nextNode = node;
}else{
System.out.println("插入失敗 \n");
}
return head;
}
/**
* 刪除結點
* 1.找到要刪除的結點位置
* 2.把前一個結點指向當前結點的后一個結點
* 3.刪除結點
* @param head
* @param key
* @return
*/
public int CLDeleteNode(CLType head,String key){
CLType node,htemp; //保存上一個結點和當前結點
htemp = head; //保存當前結點
node = head;
while(htemp!=null){
if(htemp.nextData.key.compareTo(key)==0){
node.nextNode = htemp.nextNode;
htemp = null;
return 1;
}else{
node = htemp;
htemp = htemp.nextNode;
}
}
return 0;
}
/**
* 獲取鏈表的長度
* 1.從遍歷到尾,然后進行累加
* @return
*/
public int CLLength(CLType head){
int length = 0;
CLType htemp = head;
while(htemp!=null){
length++;
htemp = htemp.nextNode;
}
return length;
}
/**
* 顯示所有結點
* @param head
*/
public void CLAllNode(CLType head){
CLType htemp;
htemp = head;
DATA2 nodeData;
while(htemp!=null){
nodeData = htemp.nextData;
System.out.printf("結點(%s,%s,%d) \n",nodeData.key,nodeData.name,nodeData.age);
htemp = htemp.nextNode;
}
}
}
上面就是鏈表結構的java代碼實現,當然只是單鏈表,還有雙鏈表,單循環鏈表,雙循環鏈表,這些我們會之后一一添加!
原來你是這樣的數據結構之棧結構