1. 程式人生 > >Java之美[從菜鳥到高手演變]之資料結構基礎、線性表、棧和佇列、陣列和字串

Java之美[從菜鳥到高手演變]之資料結構基礎、線性表、棧和佇列、陣列和字串

Java面試寶典之資料結構基礎 —— 線性表篇

作者:egg

郵箱:[email protected]

這部分內容作為計算機專業最基礎的知識,幾乎被所有企業選中用來作考題,因此,本章我們從本章開始,我們將從基礎方面對資料結構進行講解,內容主要是線性表,包括棧、佇列、陣列、字串等,主要講解基礎知識,如概念及簡單的實現程式碼,非線性結構我們在後面的文章中給出。過程中有任

一、資料結構概念

用我的理解,資料結構包含資料和結構,通俗一點就是將資料按照一定的結構組合起來,不同的組合方式會有不同的效率,使用不同的場景,如此而已。比如我們最常用的陣列,就是一種資料結構,有獨特的承載資料的方式,按順序排列,其特點就是你可以根據下標快速查詢元素,但是因為在陣列中插入和刪除元素會有其它元素較大幅度的便宜,所以會帶來較多的消耗,所以因為這種特點,使得陣列適合:查詢比較頻繁,增、刪比較少的情況,這就是資料結構的概念。資料結構包括兩大類:線性結構和非線性結構,線性結構包括:陣列、連結串列、佇列、棧等,非線性結構包括樹、圖、表等及衍生類結構。本章我們先講解線性結構,主要從陣列、連結串列、佇列、棧方面進行討論,非線性資料結構在後面會繼續講解。

二、線性表

線性表是最基本、最簡單、也是最常用的一種資料結構。線性表中資料元素之間的關係是一對一的關係,即除了第一個和最後一個數據元素之外,其它資料元素都是首尾相接的。線性表的邏輯結構簡單,便於實現和操作。因此,線性表這種資料結構在實際應用中是廣泛採用的一種資料結構。其基本操作主要有:

   1)MakeEmpty(L) 這是一個將L變為空表的方法
   2)Length(L) 返回表L的長度,即表中元素個數 
   3)Get(L,i) 這是一個函式,函式值為L中位置i處的元素(1≤i≤n)
   4)Prev(L,i) 取i的前驅元素
   5)Next(L,i) 取i的後繼元素
   6)Locate(L,x) 這是一個函式,函式值為元素x在L中的位置
   7)Insert(L,i,x)在表L的位置i處插入元素x,將原佔據位置i的元素及後面的元素都向後推一個位置
   8)Delete(L,p) 從表L中刪除位置p處的元素
   9)IsEmpty(L) 如果表L為空表(長度為0)則返回true,否則返回false
   10)Clear(L)清除所有元素
   11)Init(L)同第一個,初始化線性表為空
   12)Traverse(L)遍歷輸出所有元素
   13)Find(L,x)查詢並返回元素
   14)Update(L,x)修改元素
   15)Sort(L)對所有元素重新按給定的條件排序
   16) strstr(string1,string2)用於字元陣列的求string1中出現string2的首地址

不管採用哪種方式實現線性表,至少都應該具有上述這些基本方法,下面我會將下資料結構的基本實現方式。

三、基礎資料結構

資料結構是一種抽象的資料型別(ADT),可以這麼說,我們可以採用任意的方式實現某種資料結構,只要符合將要實現的資料結構的特點,資料結構就是一種標準,我們可以採用不同的方式去實現,最常用的兩種就是陣列和連結串列(包括單鏈表、雙向連結串列等)。陣列是非常常見的資料型別,在任何一種語言裡都有它的實現,我們這裡採用Java來簡單實現一下陣列。
陣列是一種引用型別的物件,我們可以像下面這樣的方式來宣告陣列:

	int a[];
	int[] b;
	int []c;
        a = new int[10];

總結起來,宣告一個數組有基本的三個因素:型別、名稱、下標,Java裡,陣列在格式上相對靈活,下標和名稱可以互換位置,前三種情況我們可以理解為宣告一個變數,後一種為其賦值。或者像下面這樣,在宣告的時候賦值:

	int c[] = {2,3,6,10,99};
	int []d = new int[10];

我稍微解釋一下,其實如果只執行:int[] b,只是在棧上建立一個引用變數,並未賦值,只有當執行d = new int[10]才會在堆上真正的分配空間。上述第一行為靜態初始化,就是說使用者指定陣列的內容,有系統計算陣列的大小,第二行恰恰相反,使用者指定陣列的大小,由系統分配初始值,我們列印一下陣列的初始值:

		int []d = new int[10];
		System.out.println(d[2]);
結果輸出0,對於int型別的陣列,預設的初始值為0.

但是,絕對不可以像下面這樣:

       int e[10] = new int[10];
無法通過編譯,至於為什麼,語法就是這樣,這是一種規範,不用去想它。
我們可以通過下標來檢索陣列。下面我舉個簡單的例子,來說明下陣列的用法。
	public static void main(String[] args) {

		String name[];

		name = new String[5];
		name[0] = "egg";
		name[1] = "erqing";
		name[2] = "baby";

		for (int i = 0; i < name.length; i++) {
			System.out.println(name[i]);
		}
	}
這是最簡單的陣列宣告、建立、賦值、遍歷的例子,下面寫個增刪的例子。
package com.xtfggef.algo.array;

public class Array {

	public static void main(String[] args) {

		int value[] = new int[10];
		for (int i = 0; i < 10; i++) {
			value[i] = i;
		}

		// traverse(value);
		// insert(value, 666, 5);
		delete(value, 3);
		traverse(value);
	}

	public static int[] insert(int[] old, int value, int index) {
		for (int k = old.length - 1; k > index; k--)
			old[k] = old[k - 1];
		old[index] = value;
		return old;
	}

	public static void traverse(int data[]) {
		for (int j = 0; j < data.length; j++)
			System.out.print(data[j] + " ");
	}

	public static int[] delete(int[] old, int index) {
		for (int h = index; h < old.length - 1; h++) {
			old[h] = old[h + 1];
		}
		old[old.length - 1] = 0;
		return old;
	}
}
簡單寫一下,主要想說明陣列中刪除和增加元素的原理:增加元素,需要將index後面的依次向後移動,然後將值插入index位置,刪除則將後面的值依次向前移動,較簡單。

要記住:陣列是表示相同型別的一類資料的集合,下標從0開始,就行了。

陣列實現的線下表可以參考ArrayList,在JDK中附有原始碼,感興趣的同學可以讀讀。下面我簡單介紹下單鏈表。

單鏈表是最簡單的連結串列,有節點之間首尾連線而成,簡單示意如下:


除了頭節點,每個節點包含一個數據域一個指標域,除了頭、尾節點,每個節點的指標指向下一個節點,下面我們寫個例子操作一下單鏈表。

package com.xtfggef.algo.linkedlist;

public class LinkedList<T> {

	/**
	 * class node
	 * @author egg
	 * @param <T>
	 */
	private static class Node<T> {
		T data;
		Node<T> next;

		Node(T data, Node<T> next) {
			this.data = data;
			this.next = next;
		}

		Node(T data) {
			this(data, null);
		}
	}

	// data
	private Node<T> head, tail;

	public LinkedList() {
		head = tail = null;
	}

	/**
	 * judge the list is empty
	 */
	public boolean isEmpty() {
		return head == null;
	}

	/**
	 * add head node
	 */
	public void addHead(T item) {
		head = new Node<T>(item);
		if (tail == null)
			tail = head;
	}

	/**
	 * add the tail pointer
	 */
	public void addTail(T item) {
		if (!isEmpty()) { 
			tail.next = new Node<T>(item);
			tail = tail.next;
		} else { 
			head = tail = new Node<T>(item);
		}
	}

	/**
	 * print the list
	 */
	public void traverse() {
		if (isEmpty()) {
			System.out.println("null");
		} else {
			for (Node<T> p = head; p != null; p = p.next)
				System.out.println(p.data);
		}
	}

	/**
	 * insert node from head
	 */
	public void addFromHead(T item) {
		Node<T> newNode = new Node<T>(item);
		newNode.next = head;
		head = newNode;
	}

	/**
	 * insert node from tail
	 */
	public void addFromTail(T item) {
		Node<T> newNode = new Node<T>(item);
		Node<T> p = head;
		while (p.next != null)
			p = p.next;
		p.next = newNode;
		newNode.next = null;
	}

	/**
	 * delete node from head
	 */
	public void removeFromHead() {
		if (!isEmpty())
			head = head.next;
		else
			System.out.println("The list have been emptied!");
	}

	/**
	 * delete frem tail, lower effect
	 */
	public void removeFromTail() {
		Node<T> prev = null, curr = head;
		while (curr.next != null) {
			prev = curr;
			curr = curr.next;
			if (curr.next == null)
				prev.next = null;
		}
	}

	/**
	 * insert a new node
	 * @param appointedItem
	 * @param item
	 * @return
	 */
	public boolean insert(T appointedItem, T item) {
		Node<T> prev = head, curr = head.next, newNode;
		newNode = new Node<T>(item);
		if (!isEmpty()) {
			while ((curr != null) && (!appointedItem.equals(curr.data))) {
				prev = curr;
				curr = curr.next;
			}
			newNode.next = curr; 
			prev.next = newNode;
			return true;
		}
		return false; 
	}

	public void remove(T item) {
		Node<T> curr = head, prev = null;
		boolean found = false;
		while (curr != null && !found) {
			if (item.equals(curr.data)) {
				if (prev == null)
					removeFromHead();
				else
					prev.next = curr.next;
				found = true;
			} else {
				prev = curr;
				curr = curr.next;
			}
		}
	}


	public int indexOf(T item) {
		int index = 0;
		Node<T> p;
		for (p = head; p != null; p = p.next) {
			if (item.equals(p.data))
				return index;
			index++;

		}
		return -1;
	}

	/**
	 * judge the list contains one data
	 */
	public boolean contains(T item) {
		return indexOf(item) != -1;
	}
}

單鏈表最好玩兒的也就是增加和刪除節點,下面的兩個圖分別是用圖來表示單鏈表增、刪節點示意,看著圖學習,理解起來更加容易!


接下來的佇列和棧,我們分別用不同的結構來實現,佇列用陣列,棧用單列表,讀者朋友對此感興趣,可以分別再用不同的方法實現。


四、佇列
佇列是一個常用的資料結構,是一種先進先出(First In First Out, FIFO)的結構,也就是說只能在表頭進行刪除,在表尾進行新增,下面我們實現一個簡單的佇列。

package com.xtfggef.algo.queue;

import java.util.Arrays;

public class Queue<T> {

	private int DEFAULT_SIZE = 10;
	
	private int capacity;
	
	private Object[] elementData;
	
	private int front = 0;
	private int rear = 0;
	
	public Queue()
	{
		capacity = DEFAULT_SIZE;
		elementData = new Object[capacity];
	}
	
	public Queue(T element)
	{
		this();
		elementData[0] = element;
		rear++;
	}

	public Queue(T element , int initSize)
	{
		this.capacity = initSize;
		elementData = new Object[capacity];
		elementData[0] = element;
		rear++;
	}
	
	public int size()
	{
		return rear - front;
	}
	
	public void add(T element)
	{
		if (rear > capacity - 1)
		{
			throw new IndexOutOfBoundsException("the queue is full!");
		}
		elementData[rear++] = element;
	}

    public T remove()
	{
		if (empty())
		{
			throw new IndexOutOfBoundsException("queue is empty");
		}
		
		@SuppressWarnings("unchecked")
		T oldValue = (T)elementData[front];
		
		elementData[front++] = null; 
		return oldValue;
	}
    
    @SuppressWarnings("unchecked")
	public T element()  
    {  
        if (empty())  
        {  
            throw new IndexOutOfBoundsException("queue is empty");  
        }  
        return (T)elementData[front];  
    }  
	
	public boolean empty()
	{
		return rear == front;
	}
	
	public void clear()
	{
		
		Arrays.fill(elementData , null);
		front = 0;
		rear = 0;
	}
	public String toString()
	{
		if (empty())
		{
			return "[]";
		}
		else
		{
			StringBuilder sb = new StringBuilder("[");
			for (int i = front  ; i < rear ; i++ )
			{
				sb.append(elementData[i].toString() + ", ");
			}
			int len = sb.length();
			return sb.delete(len - 2 , len).append("]").toString();
		}
	}
	public static void main(String[] args){
		Queue<String> queue = new Queue<String>("ABC", 20);
		queue.add("DEF");
		queue.add("egg");
		System.out.println(queue.empty());
		System.out.println(queue.size());
		System.out.println(queue.element());
		queue.clear();
		System.out.println(queue.empty());
		System.out.println(queue.size());
	}
}

佇列只能在表頭進行刪除,在表尾進行增加,這種結構的特點,適用於排隊系統。

五、棧

棧是一種後進先出(Last In First Out,LIFO)的資料結構,我們採用單鏈表實現一個棧。

package com.xtfggef.algo.stack;

import com.xtfggef.algo.linkedlist.LinkedList;

public class Stack<T> {

	static class Node<T> {
		T data;
		Node<T> next;

		Node(T data, Node<T> next) {
			this.data = data;
			this.next = next;
		}

		Node(T data) {
			this(data, null);
		}
	}

	@SuppressWarnings("rawtypes")
	static LinkedList list = new LinkedList();

	@SuppressWarnings("unchecked")
	public T push(T item) {
		list.addFromHead(item);
		return item;
	}

	public void pop() {
		list.removeFromHead();
	}

	public boolean empty() {
		return list.isEmpty();
	}

	public int search(T t) {
		return list.indexOf(t);
	}

	public static void main(String[] args) {
		Stack<String> stack = new Stack<String>();
		System.out.println(stack.empty());
		stack.push("abc");
		stack.push("def");
		stack.push("egg");
		stack.pop();
		System.out.println(stack.search("def"));
	}
}

本章的內容都是很基礎的,重在讓讀者朋友們理解資料結構的概念,下章開始,我們會介紹樹、二叉樹等Java中的實現,敬請讀者朋友們持續關注!

作者:egg

郵箱:[email protected]

有問題,請依照上述聯絡方式聯絡作者,歡迎讀者及時提出建議,謝謝!

相關推薦

no