1. 程式人生 > >【線性表基礎】順序表和單鏈表的插入、刪除等基本操作【Java版】

【線性表基礎】順序表和單鏈表的插入、刪除等基本操作【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!");
        }
    }
}

執行我們的測試類,得到以下結果:

以上便是線性表中順序表和單鏈表最基礎的程式碼描述,演算法思想本文中沒有寫到,大家可以去看看前面說的那本參考書籍。此係列後面會陸續介紹更多有關資料結構的內容,也會更新一些關於資料結構的演算法題目例子,謝謝大家支援