java数据接口之链表_Java数据结构和算法之链表
三、鏈表
鏈結點
在鏈表中,每個數據項都被包含在‘點“中,一個點是某個類的對象,這個類可認叫做LINK。因為一個鏈表中有許多類似的鏈結點,所以有必要用一個不同于鏈表的類來表達鏈結點。每個LINK對象中都包含一個對下一個點引用的字段(通常叫做next)但是本身的對象中有一個字段指向對第一個鏈結點的引用。
單鏈表
用一組地址任意的存儲單元存放線性表中的數據元素。
以元素(數據元素的映象) ?+ 指針(指示后繼元素存儲位置) ?= 結點(表示數據元素 或 數據元素的映象)
以“結點的序列”表示線性表,稱作線性鏈表(單鏈表)
單鏈表是一種順序存取的結構,為找第 i 個數據元素,必須先找到第 i-1 個數據元素。
因此,查找第 i 個數據元素的基本操作為:移動指針,比較 j 和 i
1、鏈接存儲方法 ?鏈接方式存儲的線性表簡稱為鏈表(Linked List)。
鏈表的具體存儲表示為:
① 用一組任意的存儲單元來存放線性表的結點(這組存儲單元既可以是連續的,也可以是不連續的)
② 鏈表中結點的邏輯次序和物理次序不一定相同。為了能正確表示結點間的邏輯關系,在存儲每個結點值的同時,還必須存儲指示其后繼結點的地址(或位置)信息(稱為指針
(pointer)或鏈(link))
注意: ?鏈式存儲是最常用的存儲方式之一,它不僅可用來表示線性表,而且可用來表示各種非線性的數據結構。
2、鏈表的結點結構
┌──--┬──--┐
│data │next│
└──---┴─--─┘
data域--存放結點值的數據域
next域--存放結點的直接后繼的地址(位置)的指針域(鏈域)
注意:
①鏈表通過每個結點的鏈域將線性表的n個結點按其邏輯順序鏈接在一起的。
②每個結點只有一個鏈域的鏈表稱為單鏈表(Single Linked List)。
【例】線性表(bat,cat,eat,fat,hat,jat,lat,mat)的單鏈表示如示意圖
3、頭指針head和終端結點指針域的表示 ?單鏈表中每個結點的存儲地址是存放在其前趨結點next域中,而開始結點無前趨,故應設頭指針head指向開始結點。
注意: ?鏈表由頭指針唯一確定,單鏈表可以用頭指針的名字來命名。
【例】頭指針名是head的鏈表可稱為表head。 ?終端結點無后繼,故終端結點的指針域為空,即NULL。
4、單鏈表的一般圖示法 ?由于我們常常只注重結點間的邏輯順序,不關心每個結點的實際位置,可以用箭頭來表示鏈域中的指針,線性表(bat,cat,fat,hat,jat,lat,mat)的單鏈表就可以表示為下圖形式。 ?bat->cat->fat->hat->jat->lat->mat
5、單鏈表類型描述
typedef char DataType; //假設結點的數據域類型為字符
typedef struct node{ //結點類型定義
DataType data; //結點的數據域
struct node *next;//結點的指針域
}ListNode
typedef ListNode *LinkList;
ListNode *p;
LinkList head;
注意:
①*LinkList和ListNode是不同名字的同一個指針類型(命名的不同是為了概念上更明確)
②*LinkList類型的指針變量head表示它是單鏈表的頭指針
③ListNode類型的指針變量p表示它是指向某一結點的指針
6、指針變量和結點變量
指針變量????????????? ?│??????????? ?結點變量
│ 定義??? ?│在變量說明部分顯式定義? │在程序執行時,通過標準函數malloc生成
│ 取值???? │ 非空時,存放某類型結點 │實際存放結點各域內容的地址
│操作方式│ 通過指針變量名訪問????? ?│ 通過指針生成、訪問和釋放
①生成結點變量的標準函數 ?p=( ListNode *)malloc(sizeof(ListNode)); ?//函數malloc分配一個類型為ListNode的結點變量的空間,并將其首地址放入指針變量p中
②釋放結點變量空間的標準函數 ?free(p);//釋放p所指的結點變量空間
③結點分量的訪問 ?利用結點變量的名字*p訪問結點分量
方法一:(*p).data和(*p).next
方法二:p-﹥data和p-﹥next
④指針變量p和結點變量*p的關系
指針變量p的值——結點地址
結點變量*p的值——結點內容
(*p).data的值——p指針所指結點的data域的值
(*p).next的值——*p后繼結點的地址
*((*p).next)——*p后繼結點
注意:
① 若指針變量p的值為空(NULL),則它不指向任何結點。此時,若通過*p來訪問結點就意味著訪問一個不存在的變量,從而引起程序的錯誤。
② 有關指針類型的意義和說明方式的詳細解釋 ?可見,在鏈表中插入結點只需要修改指針。但同時,若要在第i個結點之前插入元素,修改的是第i-1個結點的指針。
因此,在單鏈表中第i個結點之前進行插入的基本操作為: 找到線性表中第i-1個結點,然后修改其指向后繼的指針。
雙端鏈表
雙端鏈表與傳統的鏈表非常相似,但是它有一個新增的特性:即對最后一個鏈結點的引用,就像對第一個鏈結點的引用一樣。
對最后一個鏈結點的引用允許像在表頭一樣,在表尾直接插入一個鏈結點。當然,仍然可以在普通的單鏈表的表尾插入一個鏈結點,方法是遍歷整個鏈表直到到達表尾,但是這種方法效率很低。
對最后一個鏈結點的引用允許像在表頭一樣,在表尾直接插入一個鏈結點。當然,仍然可以在普通的單鏈表的表尾插入一個鏈結點,方法是遍歷整個鏈表直到到達表尾,但是這種方法效率很低。
像訪問表頭一樣訪問表尾的特性,使雙端鏈表更適合于一些普通鏈表不方便操作的場合,隊列的實現就是這樣一個情況。
下面是一個雙端鏈表的例子。
class Link3{
public long dData;
public Link3 next;
public Link3(long d){
dData=d;
}
public void displayLink(){
System.out.print(dData+" ");
}
}
//
class FirstLastList{
private Link3 first;
private Link3 last;
public FirstLastList(){
first=null;
last=null;
}
public boolean isEmpty(){
return first==null;
}
public void insertFirst(long dd){
Link3 newLink=new Link3(dd); ??.
if(isEmpty()){
last=newLink;
newLink.next=first;
first=newLink;
}
}
public void insertLast(long dd){
Link3 newLink=new Link3(dd);
if(isEmpty()){
first=newLink;
}else{
last.next=newLink;
last=newLink;
}
}
public long deleteFirst(){
long temp=first.dData;
if(first.next==null){
last=null;
first=first.next;
return temp;
}
}
public void displayList(){
System.out.print("List (first-->last): ");
Link3 current=first;
while(current!=null){
current.displayLink();
current=current.next;
}
System.out.println("");
}
}
//
public class FirstLastApp {
public static void main(String[] args){
FirstLastList theList=new FirstLastList();
theList.insertFirst(22);
theList.insertFirst(44);
theList.insertFirst(66);
theList.insertLast(11);
theList.insertLast(33);
theList.insertLast(55);
theList.displayList();
theList.deleteFirst();
theList.deleteFirst();
theList.displayList();
}
}
為了簡單起見,在這個程序中,把每個鏈結點中的數據字段個數從兩個壓縮到一個。這更容易顯示鏈結點的內容。(記住,在一個正式的程序中,可能會有非常多的數據字段,或者對另外一個對象的引用,那個對象也包含很多數據字段。)
這個程序在表頭和表尾各插入三個鏈點,顯示插入后的鏈表。然后刪除頭兩個鏈結點,再次顯示。
注意在表頭重復插入操作會顛倒鏈結點進入的順序,而在表尾的重復插入則保持鏈結點進入的順序。
雙端鏈表類叫做FirstLastList。它有兩個項,first和last,一個指向鏈表中的第一個鏈結點,另一個指向最后一個鏈結點。如果鏈表中只有一個鏈結點,first和last就都指向它,如果沒有鏈結點,兩者都為Null值。
這個類有一個新的方法insertLast(),這個方法在表尾插入一個新的鏈結點。這個過程首先改變last.next,使其指向新生成的鏈結點,然后改變last,使其指向新的鏈結點。 ? 插入和刪除方法和普通鏈表的相應部分類似。然而,兩個插入方法都要考慮一種特殊情況,即插入前鏈表是空的。如果isEmpty()是真,那么insertFirst()必須把last指向新的鏈結點,insertLast()也必須把first指向新的鏈結點。
如果用insertFirst()方法實現在表頭插入,first就指向新的鏈結點,用insertLast()方法實現在表尾插入,last就指向新的鏈結點。如果鏈表只有一個鏈結點,那么多表頭刪除也是一種特殊情況:last必須被賦值為null值。
不幸的是,用雙端鏈表也不能有助于刪除最后一個鏈結點,因為沒有一個引用指向倒數第二個鏈結點。如果最后一個鏈結點被刪除,倒數第二個鏈結點的Next字段應該變成Null值。為了方便的刪除最后一個鏈結點,需要一個雙向鏈表。(當然,也可以遍歷整個鏈表找到最后一個鏈結點,但是那樣做效率不是很高。) ?有序鏈表 ?在有序鏈表中,數據是按照關鍵值有序排列的。有序鏈表的刪除常常是只限于刪除在鏈表頭部的最小鏈結點。不過,有時也用Find()方法和Delete()方法在整個鏈表中搜索某一特定點。
一般,在大多數需要使用有序數組的場合也可以使用有序鏈表。有序鏈表優于有序數組的地方是插入的速度,另外鏈表可以擴展到全部有效的使用內存,而數組只能局限于一個固定的大小中。但是,有序鏈表實現起來比有序數組更困難一些。
后而將看到一個有序鏈表的應用:為數據排序。有序鏈表也可以用于實現優先級隊列,盡管堆是更常用的實現方法。 ? 在有序鏈表中插入一個數據項的Java代碼,為了在一個有序鏈表中插入數據項,算法必須首先搜索鏈表,直到找到合適的位置:它恰好在第一個比它大的數據項的前面。
當算法找到了要插入的位置,用通常的方式插入數據項:把新鏈結點的Next字段指向下一個鏈結點,然后把前一個鏈結點的Next字段改為指向新的鏈結點。然而,需要考慮一些特殊情況:鏈結點有可以插在表頭,或者插在表尾。看一下這段代碼:
Public void insert(long key){
Link newLink= new Link(key);
Link previous= null;
Link current=first;
While(current!= null&&key>current.dData){
Previous= current;
Current=current.next;
}
if(previous== null){
First=newLink;
}else{
Previous.next= newLink;
newLink.next=current;
}
在鏈表上移動時,需要用一個previous引用,這樣才能把前一個鏈結點的Next字段指向新的鏈結點。創建新鏈結點后,把current變量設為first,準備搜索正確的插入點。這時也把previous設為Null值,這步操作很重要,因為后面要用這個Null值判斷是否仍在表頭。
While循環和以前用來搜索插入點的代碼類似,但是有一個附加的條件。如果當前檢查的鏈結點的關鍵值不再小于待插入的鏈結點的關鍵值,則循環結束;這是最常見的情況,即新關鍵值插在鏈表中部的某個地方。 然而,如果current為Null值,while循環也會停止。這種情況發生在表尾,或者鏈表為空時。
如果current在表頭或者鏈表為空,previous將為Null值;所以讓first指向新的鏈結點。否則current處在鏈表中部或結尾,就使previous的next字段指向新的鏈結點。 不論哪種情況、都讓新鏈結點的Next字段指向current。如果在表尾,current為Null值,則新鏈結點的Next字段也本應該設為這個值(Null)。
下面是有序鏈表的程序 ?SortedList.java程序實現了一個SortedList類,它擁有insert()、remove()和displayList()方法。只有insert()方法與無序鏈表中的insert()方法不同。
package 有序鏈表;
class Link{
public long dData;
public Link next;
public Link(long dd){
dData= dd;
}
//.........................
public void displayLink(){
System.out.print(dData+" ");
}
}
class SortedList{
private Link first;
//..........................
public SortedList(){
first=null;
}
//.........................
public boolean isEmpty(){
return (first== null);
}
//..........................
public void insert(long key){
Link newLink=new Link(key);
Link previous= null;
Link current= first;
while(current!=null&&key>current.dData){
previous=current;
current=current.next;
}
if(previous== null){
first=newLink;
} else{
previous.next= newLink;
newLink.next=current;
}
//................................
public Link remove(){
Link temp=first;
first= first.next;
return temp;
}
//................................
public void displayList(){
System.out.print("List (first-->last): ");
Link current= first;
while(current!=null){
current.displayLink();
current= current.next;
}
System.out.println("");
}
}
public class SortedLinkApp{
public static void main(String[] args){
SortedList theSortedList=new SortedList();
theSortedList.insert(20);
theSortedList.insert(40);
theSortedList.displayList();
theSortedList.insert(10);
theSortedList.insert(30);
theSortedList.insert(50);
theSortedList.displayList();
theSortedList.remove();
theSortedList.displayList();
System.exit(0);
}
}
在Main()方法中,插入值為20和40的兩個鏈結點。然后再插入三個鏈結點,分別是10、30和50。這三個值分別插在表頭、表中和表尾。這說明insert()方法正確地處理了特殊情況。最后刪除了一個鏈結點,表現出刪除操作總是從表頭進行。每一步變化后,都顯示整個鏈表。
雙向鏈表
雙向鏈表也叫雙鏈表,是鏈表的一種,它的每個數據結點中都有兩個指針,分別指向直接后繼和直接前驅。所以,從雙向鏈表中的任意一個結點開始,都可以很方便地訪問它的前驅結點和后繼結點。一般我們都構造雙向循環鏈表。
/* 線性表的雙向鏈表存儲結構 */
typedef struct DuLNode{
ElemType data;
struct DuLNode *prior,*next;
}DuLNode,*DuLinkList;
/*帶頭結點的雙向循環鏈表的基本操作(14個) */
void InitList(DuLinkList *L){/* 產生空的雙向循環鏈表L */
*L=(DuLinkList)malloc(sizeof(DuLNode)); ??if(*L){
(*L)->next=(*L)->prior=*L;
}else{
exit(OVERFLOW);
}
void DestroyList(DuLinkList *L){
/* 操作結果:銷毀雙向循環鏈表L */
DuLinkList q,p=(*L)->next;/* p指向第一個結點 */
while(p!=*L){
/* p沒到表頭 */
q=p->next;
free(p);
p=q;
}
free(*L);
*L=NULL;
}
void ClearList(DuLinkList L) {/* 不改變L */
/* 初始條件:L已存在。操作結果:將L重置為空表 */
DuLinkList q,p=L->next; /* p指向第一個結點 */
while(p!=L) {/* p沒到表頭 */
q=p->next;
free(p);
p=q;
}
L->next=L->prior=L; /* 頭結點的兩個指針域均指向自身 */
}
Status ListEmpty(DuLinkList L){ /* 初始條件:線性表L已存在。操作結果:若L為空表,則返回TRUE,否則返回FALSE */
if(L->next==L&&L->prior==L){
return TRUE;
}else{
return FALSE;
}
int ListLength(DuLinkList L){ ?/* 初始條件:L已存在。操作結果:返回L中數據元素個數 */
int i=0;
DuLinkList p=L->next; /* p指向第一個結點 */
while(p!=L) {/* p沒到表頭 */
i++;
p=p->next;
}
return i;
}
Status GetElem(DuLinkList L,int i,ElemType *e){
/* 當第i個元素存在時,其值賦給e并返回OK,否則返回ERROR */
int j=1; /* j為計數器 */
DuLinkList p=L->next; /* p指向第一個結點 */
while(p!=L&&jnext){
j++;
}
if(p==L||j>i) /* 第i個元素不存在?{ */
return ERROR;
}
*e=p->data; /* 取第i個元素 */
return OK;
}
int LocateElem(DuLinkList L,ElemType e,Status(*compare)(ElemType,ElemType)){
/* 初始條件:L已存在,compare()是數據元素判定函數 */
/* 操作結果:返回L中第1個與e滿足關系compare()的數據元素的位序。 */
/* 若這樣的數據元素不存在,則返回值為0 */
int i=0;
DuLinkList p=L->next;/* p指向第1個元素 */
while(p!=L){
i++;
if(compare(p->data,e)){ /* 找到這樣的數據元素 */
return i;
p=p->next;
}
return 0;
}
Status PriorElem(DuLinkList L,ElemType cur_e,ElemType *pre_e) ? { /* 操作結果:若cur_e是L的數據元素,且不是第一個,則用pre_e返回它的前驅, */
/* 否則操作失敗,pre_e無定義 */
DuLinkList p=L->next->next; /* p指向第2個元素 */
while(p!=L){ /* p沒到表頭*/
if(p->data==cur_e){
*pre_e=p->prior->data;
return TRUE;
}
p=p->next;
}
return FALSE;
}
Status NextElem(DuLinkList L,ElemType cur_e,ElemType *next_e){
/* 操作結果:若cur_e是L的數據元素,且不是最后一個,則用next_e返回它的后繼, */
/* 否則操作失敗,next_e無定義 */
DuLinkList p=L->next->next; /* p指向第2個元素 */
while(p!=L){ /* p沒到表頭 */
if(p->prior->data==cur_e) ?? {
*next_e=p->data;
return TRUE;
}
p=p->next;
}
return FALSE;
}
DuLinkList GetElemP(DuLinkList L,int i) {
/* 另加 */
/* 在雙向鏈表L中返回第i個元素的地址。i為0,返回頭結點的地址。若第i個元素不存在,*/
/* 返回NULL */
int j;
DuLinkList p=L; /* p指向頭結點 */
if(i<0||i>ListLength(L)){ /* i值不合法 */
return NULL;
}
for(j=1;j<=i;j++){
p=p->next;
return p;
}
}
Status ListInsert(DuLinkList L,int i,ElemType e) ? { /* 在帶頭結點的雙鏈循環線性表L中第i個位置之前插入元素e,i的合法值為1≤i≤表長+1 */
/* 改進算法2.18,否則無法在第表長+1個結點之前插入元素 */
DuLinkList p,s;
if(i<1||i>ListLength(L)+1){ /* i值不合法 */
return ERROR;
p=GetElemP(L,i-1); /* 在L中確定第i個元素前驅的位置指針p */
}
if(!p) {/* p=NULL,即第i個元素的前驅不存在(設頭結點為第1個元素的前驅) */
return ERROR;
s=(DuLinkList)malloc(sizeof(DuLNode));
}
if(!s){
return OVERFLOW;
}
s->data=e;
s->prior=p; /* 在第i-1個元素之后插入 */
s->next=p->next;
p->next->prior=s;
p->next=s;
return OK;
}
Status ListDelete(DuLinkList L,int i,ElemType *e){ /* 刪除帶頭結點的雙鏈循環線性表L的第i個元素,i的合法值為1≤i≤表長 */
DuLinkList p;
if(i<1){ /* i值不合法 */
return ERROR;
}
p=GetElemP(L,i); /* 在L中確定第i個元素的位置指針p */
if(!p) {/* p=NULL,即第i個元素不存在 */
return ERROR;
}
*e=p->data;
p->prior->next=p->next;
p->next->prior=p->prior;
free(p);
return OK;
}
void ListTraverse(DuLinkList L,void(*visit)(ElemType)) ? { /* 由雙鏈循環線性表L的頭結點出發,正序對每個數據元素調用函數visit() */
DuLinkList p=L->next; /* p指向頭結點 */
while(p!=L)?? {
visit(p->data);
p=p->next;
}
printf("\n");
}
void ListTraverseBack(DuLinkList L,void(*visit)(ElemType)) ? { /* 由雙鏈循環線性表L的頭結點出發,逆序對每個數據元素調用函數visit()。另加 */
DuLinkList p=L->prior; /* p指向尾結點 */
while(p!=L) ?? {
visit(p->data);
p=p->prior;
}
printf("\n");
}
迭代器
迭代器是一種對象,它能夠用來遍歷STL容器中的部分或全部元素,每個迭代器對象代表容器中的確定的地址。迭代器修改了常規指針的接口,所謂迭代器是一種概念上的抽象:那些行為上象迭代器的東西都可以叫做迭代器。然而迭代器有很多不同的能力,它可以把抽象容器和通用算法有機的統一起來。
迭代器提供一些基本操作符:*、++、==、!=、=。這些操作和C/C++“操作array元素”時的指針接口一致。不同之處在于,迭代器是個所謂的smart pointers,具有遍歷復雜數據結構的能力。其下層運行機制取決于其所遍歷的數據結構。因此,每一種容器型別都必須提供自己的迭代器。事實上每一種容器都將其迭代器以嵌套的方式定義于內部。因此各種迭代器的接口相同,型別卻不同。這直接導出了泛型程序設計的概念:所有操作行為都使用相同接口,雖然它們的型別不同。
功能 ? 迭代器使開發人員能夠在類或結構中支持foreach迭代,而不必整個實現IEnumerable或者IEnumerator接口。只需提供一個迭代器,即可遍歷類中的數據結構。當編譯器檢測到迭代器時,將自動生成IEnumerable接口或者IEnumerator接口的Current,MoveNext和Dispose方法。
特點
1.迭代器是可以返回相同類型值的有序序列的一段代碼;
2.迭代器可用作方法、運算符或get訪問器的代碼體;
3.迭代器代碼使用yield return語句依次返回每個元素,yield break將終止迭代;
4.可以在類中實現多個迭代器,每個迭代器都必須像任何類成員一樣有惟一的名稱,并且可以在foreach語句中被客戶端代碼調用;
5.迭代器的返回類型必須為IEnumerable和IEnumerator中的任意一種;
6.迭代器是產生值的有序序列的一個語句塊,不同于有一個 或多個yield語句存在的常規語句塊;
7.迭代器不是一種成員,它只是實現函數成員的方式,理解這一點是很重要的,一個通過迭代器實現的成員,可以被其他可能或不可能通過迭代器實現的成員覆蓋和重載;
8.迭代器塊在C#語法中不是獨特的元素,它們在幾個方面受到限制,并且主要作用在函數成員聲明的語義上,它們在語法上只是語句塊而已;
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的java数据接口之链表_Java数据结构和算法之链表的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java bufferedinputst
- 下一篇: mysql5.7命中率_MySQL5.7