數據結構與算法

數據結構與算法

1. 什麼是數據結構?

計算機組織與存儲數據的邏輯結構,目的是爲了實現高效的數據訪問與修改。

常見的數據結構有數組、鏈表、隊列、棧、二叉樹、散列表、圖等等。

2.什麼是算法?

爲了解決特點的問題,對數據進行加工的步驟

常見的算法有排序、查找

算法一般包含了輸入、輸出、有限的步驟、確定性

3. 數據結構與算法的關係?

程序 = 數據結構 + 算法

注:任何程序說到底都是對數據進行加工與處理,這個加工與處理所涉及的步驟就是算法,

而爲了對這些數據實現高效的訪問和修改,就需要使用相應的數據結構


雙向循環鏈表

數組:

​ 優點:可以利用下標快速的找到某個元素!

​ 缺點:插入,刪除元素時需要對整個數組進行調整

鏈表:

​ 優點:插入,刪除元素非常快

​ 缺點:查找慢,只能順序查找

除此以外,數組長度固定、需要連續的地址空間,而鏈表長度不固定,不需要連續的地址空間

  • 什麼是雙向循環鏈表

在這裏插入圖片描述

雙向循環鏈表由一系列節點構成,其中每個節點包含兩部分內容,一個是元素(即數據),另外一個是指向前驅節點和後繼節點的指針。另外,頭節點和尾節點也彼此指向。

注:JDK8之前,LinkedList內部採用的雙向循環鏈表來實現的,JDK8之後改成了雙向鏈表來實現。

搭建雙向循環鏈表的結構

package list;

/**
 * 雙向循環鏈表
 */
public class LinkedList<E> {
	//雙向循環鏈表的頭
	private Node head; 
	
	public boolean add(E e) {
        //
	}
	
	/**
	 * 內部節點類(將元素封裝成節點的類)
	 */
	class Node{
		E data; // 存放的數據
		Node next; // 後繼節點
		Node prev; // 前驅節點
		Node(E e){
			data = e;
		}
	}
}

add(E e)方法

添加元素到鏈表的末尾

添加成功則返回true,添加失敗則返回false

在這裏插入圖片描述

package list;

/**
 * 雙向循環鏈表
 */
public class LinkedList<E> {
	//雙向循環鏈表的頭
	private Node head; 
	
	/**
	 * 將一個元素添加到鏈表的末尾
	 * @param e 被添加的元素
	 * @return 添加成功,返回true
	 */
	public boolean add(E e) {
		// 將元素封裝成節點
		Node node = new Node(e);
		// 查看當前鏈表是否爲空
		if(head == null) {
			head = node;
			// 如果當前只有這一個節點,則它的前驅結點跟後繼節點都指向它自己
			head.next = node;
			head.prev = node;
			return true;
		}
		// 如果鏈表不爲空,則先找到尾節點
		Node last = head.prev;
		// 將新節點添加進來(重新建立新的引用關係)
		last.next = node;
		node.next = head;
		head.prev = node;
		node.prev = last;
		return true;
	}
	
	/**
	 * 內部節點類(將元素封裝成節點的類)
	 */
	class Node{
		E data; // 存放的數據
		Node next; // 後繼節點
		Node prev; // 前驅節點
		Node(E e){
			data = e;
		}
	}
}

toString方法

爲了方便查看雙向循環鏈表的數據

如果頭節點爲空,則沒有數據,輸出[]

否則依次遍歷鏈表,當遍歷的節點==頭節點時結束循環

@Override
public String toString() {
    if(head == null) {
        return "[]";
    }
    StringBuilder sb = new StringBuilder("[");
    sb.append(head.data);
    Node node = head.next;
    while(node != head) {
        sb.append(","+node.data);
        node = node.next;
    }
    sb.append("]");
    return sb.toString();
}

int size()方法

返回雙向循環鏈表的長度

/**
  * 返回節點的個數,雙向循環鏈表的長度
  * @return 雙向循環鏈表的長度
  */
public int size() {
    if(head == null) {
        return 0;
    }
    int size = 1;
    Node node = head.next;
    while(node != head) {
        size += 1;
        node = node.next;
    }
    return size;
}

注意:上面這種做法要遍歷鏈表,如果鏈表特別長的話,耗時,可以進行優化

設置一個全局屬性size,每次做add(E e)操作時,size加1;每次做remove操作時,size減1

package list;

/**
 * 雙向循環鏈表
 */
public class LinkedList<E> {
	//雙向循環鏈表的頭
	private Node head; 
	// 存放鏈表的長度(節點的格式)
	private int size;
	
	/**
	 * 將一個元素添加到鏈表的末尾
	 * @param e 被添加的元素
	 * @return 添加成功,返回true
	 */
	public boolean add(E e) {
		// 將元素封裝成節點
		Node node = new Node(e);
		// 查看當前鏈表是否爲空
		if(head == null) {
			head = node;
			// 如果當前只有這一個節點,則它的前驅結點跟後繼節點都指向它自己
			head.next = node;
			head.prev = node;
			// 將鏈表中節點的個數加1
			size++;
			return true;
		}
		// 如果鏈表不爲空,則先找到尾節點
		Node last = head.prev;
		// 將新節點添加進來(重新建立新的引用關係)
		last.next = node;
		node.next = head;
		head.prev = node;
		node.prev = last;
		// 將鏈表中節點的個數加1
		size++;
		return true;
	}
	
	/**
	 * 返回鏈表當中節點的個數
	 * @return 鏈表的長度
	 */
	public int size() {
		return size;
	}
	
	/**
	 * 內部節點類(將元素封裝成節點的類)
	 */
	class Node{
		E data; // 存放的數據
		Node next; // 後繼節點
		Node prev; // 前驅節點
		Node(E e){
			data = e;
		}
	}
}

get(int index)方法

返回指定下標的元素

在這裏插入圖片描述

/**
  * 返回指定下標的元素
  * @param index 下標位置
  * @return 元素
  */
public E get(int index) {
    if(index < 0 || index >= size) {
        throw new IndexOutOfBoundsException("下標越界!");
    }
    Node node = head;
    for(int i=0; i<index; i++) {
        node = node.next;
    }
    return node.data;
}

注意:這麼寫就夠了。如果數量特別大,下標位置又比較靠後,比如遍歷倒數第二個,則順序遍歷耗時

可以進一步優化

可以用二分法進行查找,進行反向查詢

/**
  * 返回指定下標的元素
  * @param index 下標位置
  * @return 元素
  */
public E get(int index) {
    if(index < 0 || index >= size) {
        throw new IndexOutOfBoundsException("下標越界!");
    }
    Node node = head;
    if(index < size/2) {
        for(int i=0; i<index; i++) {
            node = node.next;
        }
    }else {
        for(int i=size; i>index; i--) {
            node = node.prev;
        }
    }
    return node.data;
}

由於刪除操作也是要找到對應位置的節點。所以可以把上面找到位置的節點代碼封裝成一個小方法

/**
  * 返回指定下標的元素
  * @param index 下標位置
  * @return 元素
  */
public E get(int index) {
    if(index < 0 || index >= size) {
        throw new IndexOutOfBoundsException("下標越界!");
    }
    Node node = getNode(index);
    return node.data;
}
	
/**
  * 返回指定下標出的節點
  */
private Node getNode(int index) {
    Node node = head;
    if(index < size/2) {
        for(int i=0; i<index; i++) {
            node = node.next;
        }
    }else {
        for(int i=size; i>index; i--) {
            node = node.prev;
        }
    }
    return node;
}

remove(int index)方法

刪除指定下標的節點

返回被刪除的節點元素

注意下標越界的情況

注意整個鏈表只有一個節點的情況(刪掉就爲null了)

注意如果把頭節點刪掉了,要重新指定新的頭節點

注意刪除後size要減1

分析:

​ 例如:[8, 22, 16, 11, 33]

​ 可以用getNode(index)方法得到要刪除的那個節點node(比如16)

​ node.next得到後繼節點(11)

​ node.prev得到前驅節點(22)

​ 重新建立連接(11和22重新建立節點連接)

在這裏插入圖片描述

/**
  * 刪除指定下標的節點
  * @param index 下標位置
  * @return 被刪除的節點的元素
  */
public E remove(int index) {
    if(index < 0 || index >= size) {
        throw new IndexOutOfBoundsException("下標越界!");
    }

    // 考慮鏈表長度爲1的情況
    if(size == 1) {
        // 長度爲1,則只有head這一個節點,刪除直接設置爲null即可
        E data = head.data;
        head = null;
        size--;
        return data;
    }

    // 找到要刪除的節點
    Node node = getNode(index);
    // 找到其前驅節點和後繼節點
    Node nodePrev = node.prev;
    Node nodeNext = node.next;
    // 重新建立節點連接
    nodeNext.prev = nodePrev;
    nodePrev.next = nodeNext;
    // 考慮刪除頭節點的情況,則要將下一個節點設置爲頭節點
    if(index == 0) {
        head = nodeNext;
    }
    // 鏈表的長度減1
    size--;
    // 返回被刪除的節點元素
    return node.data;
}

add(int index, E e)方法

在指定位置上插入元素

用getNode(index)方法得到下標爲index的那個節點node(比如16)

找到其前驅節點prev(22)

重新進行節點關聯

注意特殊情況(index=0),則頭節點改變了

在這裏插入圖片描述

/**
  * 在指定位置上插入元素
  * @param index 下標位置
  * @param e 要插入的元素
  */
public void add(int index, E e) {
    if(index < 0 || index >= size) {
        throw new IndexOutOfBoundsException("下標越界!");
    }

    // 封裝要插入的元素節點
    Node node = new Node(e);

    // 先找到下標爲index的節點
    Node nodeNext = getNode(index);
    // 找到其前驅結點
    Node nodePrev = nodeNext.prev;
    // 重新進行節點的關聯
    nodeNext.prev = node;
    node.prev = nodePrev;
    nodePrev.next = node;
    node.next = nodeNext;
    if(index == 0) {
        head = node;
    }
}

完整代碼如下:

package list;

/**
 * 雙向循環鏈表
 */
public class LinkedList<E> {
	//雙向循環鏈表的頭
	private Node head; 
	// 存放鏈表的長度(節點的格式)
	private int size;
	
	/**
	 * 將一個元素添加到鏈表的末尾
	 * @param e 被添加的元素
	 * @return 添加成功,返回true
	 */
	public boolean add(E e) {
		// 將元素封裝成節點
		Node node = new Node(e);
		// 查看當前鏈表是否爲空
		if(head == null) {
			head = node;
			// 如果當前只有這一個節點,則它的前驅結點跟後繼節點都指向它自己
			head.next = node;
			head.prev = node;
			// 將鏈表中節點的個數加1
			size++;
			return true;
		}
		// 如果鏈表不爲空,則先找到尾節點
		Node last = head.prev;
		// 將新節點添加進來(重新建立新的引用關係)
		last.next = node;
		node.next = head;
		head.prev = node;
		node.prev = last;
		// 將鏈表中節點的個數加1
		size++;
		return true;
	}
	
	/**
	 * 返回鏈表當中節點的個數
	 * @return 鏈表的長度
	 */
	public int size() {
		return size;
	}
	
	/**
	 * 返回指定下標的元素
	 * @param index 下標位置
	 * @return 元素
	 */
	public E get(int index) {
		if(index < 0 || index >= size) {
			throw new IndexOutOfBoundsException("下標越界!");
		}
		Node node = getNode(index);
		return node.data;
	}
	
	/**
	 * 刪除指定下標的節點
	 * @param index 下標位置
	 * @return 被刪除的節點的元素
	 */
	public E remove(int index) {
		if(index < 0 || index >= size) {
			throw new IndexOutOfBoundsException("下標越界!");
		}
		
		// 考慮鏈表長度爲1的情況
		if(size == 1) {
			// 長度爲1,則只有head這一個節點,刪除直接設置爲null即可
			E data = head.data;
			head = null;
			size--;
			return data;
		}
		
		// 找到要刪除的節點
		Node node = getNode(index);
		// 找到其前驅節點和後繼節點
		Node nodePrev = node.prev;
		Node nodeNext = node.next;
		// 重新建立節點連接
		nodeNext.prev = nodePrev;
		nodePrev.next = nodeNext;
		// 考慮刪除頭節點的情況,則要將下一個節點設置爲頭節點
		if(index == 0) {
			head = nodeNext;
		}
		// 鏈表的長度減1
		size--;
		// 返回被刪除的節點元素
		return node.data;
	}
	
	/**
	 * 在指定位置上插入元素
	 * @param index 下標位置
	 * @param e 要插入的元素
	 */
	public void add(int index, E e) {
		if(index < 0 || index >= size) {
			throw new IndexOutOfBoundsException("下標越界!");
		}
		
		// 封裝要插入的元素節點
		Node node = new Node(e);
		
		// 先找到下標爲index的節點
		Node nodeNext = getNode(index);
		// 找到其前驅結點
		Node nodePrev = nodeNext.prev;
		// 重新進行節點的關聯
		nodeNext.prev = node;
		node.prev = nodePrev;
		nodePrev.next = node;
		node.next = nodeNext;
		if(index == 0) {
			head = node;
		}
	}
	
	/**
	 * 返回指定下標出的節點
	 */
	private Node getNode(int index) {
		Node node = head;
		if(index < size/2) {
			for(int i=0; i<index; i++) {
				node = node.next;
			}
		}else {
			for(int i=size; i>index; i--) {
				node = node.prev;
			}
		}
		return node;
	}
	
	@Override
	public String toString() {
		if(head == null) {
			return "[]";
		}
		StringBuilder sb = new StringBuilder("[");
		sb.append(head.data);
		Node node = head.next;
		while(node != head) {
			sb.append(","+node.data);
			node = node.next;
		}
		sb.append("]");
		return sb.toString();
	}




	/**
	 * 內部節點類(將元素封裝成節點的類)
	 */
	class Node{
		E data; // 存放的數據
		Node next; // 後繼節點
		Node prev; // 前驅節點
		Node(E e){
			data = e;
		}
	}
}





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章