【線性表基礎】順序表和單鏈表的插入、刪除等基本操作【Java版】
本文表述了線性表及其基本操作的程式碼【Java實現】
參考書籍 :《資料結構 ——Java語言描述》/劉小晶 ,杜選主編
線性表需要的基本功能有:動態地增長或收縮;對線性表的任何資料元素進行訪問和查詢;線上性表中的任何位置進行資料元素的插入和刪除操作;求線性表中指定資料元素的前驅和後繼等等。
首先描述線性表的抽象型別,我們使用Java介面interface:
Ilist.java:
package liner_list; public interface IList { public void clear(); public boolean isEmpty(); public int length(); public Object get(int i) throws Exception; public void insertAt(int i,Object x) throws Exception; public void remove(int i) throws Exception; public int indexOf(Object x); public void display(); }
其次描述順序表,其特點有:線上性表中的邏輯上相鄰的資料元素,在物理儲存位置上也是相鄰的;儲存密度高,但需要預先分配”足夠應用“的儲存空間,這可能將會造成儲存空間的浪費;便於隨機儲存;不便於插入和刪除,因為在順序表中進行插入和刪除操作會引起大量資料元素的移位。我們用SqList類描述順序表:
SqList.java:
package liner_list; // 規定方法中的引數i都為順序表元素的索引(下標) public class SqList implements IList { public Object[] listItem; // 順序表儲存空間 public int curLen; // 線性表的當前長度 public SqList(int maxSize) { listItem = new Object[maxSize]; // 為順序表分配maxSize個儲存單元 curLen = 0; // 置當前長度為0 } public void clear() { curLen = 0; // 置當前長度為0,即規定為清空順序表,但是記憶體中還有資料存在 } public boolean isEmpty() { return curLen == 0; } public int length() { return curLen; // 返回當前長度 } public Object get(int i) throws Exception // 得到下標為i的元素,同時判斷異常 { if (i >= curLen || i < 0) // 索引越界,0<=index<=curLen { throw new Exception("Argument 'i' is out of range!"); } return listItem[i]; } public void insertAt(int i, Object x) throws Exception // 在下表為i的位置插入元素x,同時判斷異常 { if (curLen == listItem.length) // 判斷表滿 { throw new Exception("SqList is full!"); } if (i > curLen || i < 0) // 索引越界,可以在curLen的位置進行插入 { throw new Exception("Argument 'i' is out of range!"); } for (int j = curLen; j > i; j--) // j從curLen的位置開始,即當前表最後一個元素的後一個位置,從而使得i位置及以後位置上的元素向後移一位 { listItem[j] = listItem[j - 1]; } listItem[i] = x; // 將x元素插入i位置 curLen++; // 插入後表長加一 } public void remove(int i) throws Exception { if (i >= curLen || i < 0) // i小於0或者大於等於表長時丟擲異常 { throw new Exception("Argument 'i' is out of range!"); } for (int j = i; j < curLen - 1; j++) // 從i位置開始向後,不能從最後開始,否則最後一個元素將覆蓋所有元素,若想從後向前,必須將被覆蓋的元素保留給下一個元素 { listItem[j] = listItem[j + 1]; } curLen--; // 刪除完後curLen減一 } public int indexOf(Object x) // 規定返回-1表示未找到元素x { for (int i = 0; i < curLen; i++) { if (listItem[i].equals(x)) { return i; } } return -1; // 書本程式碼,效果相同 // int j = 0; // while (j < curLen && !listItem[j].equals(x)) // { // j++; // } // if (j < curLen) // { // return j; // } else // { // return -1; // } } public void display() // 輸出順序表中全部元素 { System.out.println("****** SqList ******"); for (int i = 0; i < curLen; i++) { System.out.print(listItem[i] + " "); } System.out.println(); System.out.println("********************"); } }
接著測試我們的順序表,使用SqListTest類來做測試:
SqListTest.java:
package liner_list; import java.util.Scanner; public class SqListTest { public static void main(String[] args) throws Exception { SqList sq1 = new SqList(10); sq1.insertAt(0, "a0"); sq1.insertAt(1, "a1"); sq1.insertAt(2, "a2"); sq1.insertAt(3, "a3"); sq1.insertAt(4, "a4"); sq1.insertAt(5, "a5"); int index = sq1.indexOf("a2"); if (index != -1) { System.out.println("a2's index is " + index + "!"); } else { System.out.println("a5 is not in this SqList!"); } sq1.display(); sq1.remove(2); System.out.println("After remove:"); sq1.display(); SqList sq2 = new SqList(10); Scanner sc = new Scanner(System.in); System.out.println("Please input element:"); for (int i = 0; i < 8; i++) { sq2.insertAt(i, sc.next()); } sc.close(); sq2.display(); } }
執行我們的測試類,得到以下測試結果:
然後描述單鏈表,注意:我們推薦使用帶頭結點的單鏈表。這裡總結以下關於頭指標和頭結點的問題:首先要清楚,head就是頭指標,毋庸置疑;如果有頭結點的話,head也頭結點,這裡頭指標就是頭結點,一般說成頭指標指向頭結點,而head.next是下標為0的元素,規定 head是下標為-1的元素;如果沒有頭結點的話,head本是就是下標為0的元素,這裡沒有頭結點,但是head還是頭指標。下面我們使用LinkList類來描述帶頭結點的單鏈表:
LinkList.java:
package liner_list;
import java.util.Scanner;
//關於頭結點與頭指標的問題
//首先要清楚,head就是頭指標,毋庸置疑
//如果有頭結點的話,head也頭結點,這裡頭指標就是頭結點,一般說成頭指標指向頭結點,而head.next是下標為0的元素,規定 head是下標為-1的元素
//如果沒有頭結點的話,head本是就是下標為0的元素,這裡沒有頭結點,但是head還是頭指標
//建議寫帶頭結點的單鏈表,此類就是一個典例
public class LinkList implements IList
{
public Node head;
public LinkList() // 無參構造方法,只構建頭指標
{
head = new Node();
}
public LinkList(int len, boolean Order) throws Exception // 帶有兩個引數的構造方法,分別為表長和插入的方式,規定true表示尾插法,flase表示頭插法
{
this();
if (Order)
{
createAtEnd(len);
} else
{
createAtHead(len);
}
}
public void createAtHead(int n) throws Exception // 頭插法
{
Scanner sc = new Scanner(System.in);
System.out.println("Please input element:");
for (int i = 0; i < n; i++)
{
insertAt(0, sc.next());
}
// sc.close(); // 不要關閉輸入流
// display();
}
public void createAtEnd(int n) throws Exception // 尾插法
{
Scanner sc = new Scanner(System.in);
System.out.println("Please input element:");
for (int i = 0; i < n; i++)
{
insertAt(length(), sc.next());
}
// sc.close(); // 不要關閉輸入流
// display();
}
@Override
public void clear() // 置空連結串列
{
head.data = null;
head.next = null;
}
@Override
public boolean isEmpty() // 連結串列判空
{
return head.next == null;
}
@Override
public int length() // 返回連結串列長度
{
Node p = head.next; // p指向首結點
int length = 0;
while (p != null)
{
p = p.next;
length++;
}
return length;
}
@Override
public Object get(int i) throws Exception
{
Node p = head.next;
int j = 0;
while (p != null && j < i) // 從首結點開始向後查詢,直到p指向第i個結點或者p為空
{
p = p.next; // 指標後移
j++; // 計數加1
}
if (i < 0 || p == null) // i小於0或者大於表長減1時丟擲異常
{
throw new Exception("Argument 'i' is out of range!");
}
return p.data;
}
@Override
public int indexOf(Object x) // 規定-1表示不在LinkList當中
{
Node p = head.next;
int index = 0;
while (p != null && !p.data.equals(x))
{
p = p.next;
index++;
}
if (p != null)
{
return index;
} else
{
return -1;
}
}
@Override
public void insertAt(int i, Object x) throws Exception
{
Node p = head; // 插入時從頭結點開始,因為可以插入在下標0的位置,也可以插入在下標為表長位置
int j = -1;
while (p != null && j < i - 1) // 找出下標為i-1的結點,即i結點的前驅
{
p = p.next;
j++;
}
if (i < 0 || p == null) // i小於0或者i大於表長時丟擲異常
{
throw new Exception("Argument 'i' is out of range!");
}
Node s = new Node(x);
s.next = p.next;
p.next = s;
}
@Override
public void remove(int i) throws Exception
{
Node p = head;
int j = -1;
while (p != null && j < i - 1) // 找到下標為i-1的結點,即i結點的前驅
{
p = p.next;
j++;
}
if (i < 0 || p.next == null) // 丟擲條件為i小於0或者i大於表長-1,所以此處為p.next==null
{
throw new Exception("Argument 'i' is out of range!");
}
p.next = p.next.next;
}
@Override
public void display()
{
Node p = head.next;
System.out.println("****** LinkList ******");
while (p != null)
{
System.out.print(p.data.toString() + " ");
p = p.next;
}
System.out.println();
System.out.println("*********************");
}
}
最後測試我們的單鏈表,使用LinkListTest類來做測試:
LinkListTest.java
package liner_list;
public class LinkListTest
{
public static void main(String[] args) throws Exception
{
LinkList linkList = new LinkList(10, true);
linkList.remove(0);
linkList.remove(1);
linkList.remove(linkList.length() - 1);
System.out.println("After remove:");
linkList.display();
linkList.insertAt(linkList.length(), "a9");
System.out.println("After insert:");
linkList.display();
int index = linkList.indexOf("a2");
if (index != -1)
{
System.out.println("a2's index is " + index + "!");
} else
{
System.out.println("a2 is not in this LinkList!");
}
}
}
執行我們的測試類,得到以下結果:
以上便是線性表中順序表和單鏈表最基礎的程式碼描述,演算法思想本文中沒有寫到,大家可以去看看前面說的那本參考書籍。此係列後面會陸續介紹更多有關資料結構的內容,也會更新一些關於資料結構的演算法題目例子,謝謝大家支援