鏈表通常由一連串節點組成,每個節點包含任意的實例數據(data fields)和一或兩個用來指向上 一個/或下一個節點的位置的鏈接(“links”)
鏈表(Linked list):是一種常見的基礎數據結構,是一種線性表,但是並不會按線性的順序存儲數 據,而是在每一個節點裏存到下一個節點的指針(Pointer)。
使用鏈表結構可以克服數組需要預先知道數據大小的缺點,鏈表結構可以充分利用計算機內存空間,實現靈活的內存動態管理。但是鏈表失去了數組隨機讀取的優點,同時鏈表由於增加了結點的指針
域,空間開銷比較大。
單向鏈表
單鏈表是鏈表中結構最簡單的。一個單鏈表的節點(Node)分爲兩個部分,第一個部分(data)保存或者顯示關於節點的信息,另一個部分存儲下一個節點的地址。最後一個節點存儲地址的部分指向空值。
單向鏈表只可向一個方向遍歷,一般查找一個節點的時候需要從第一個節點開始每次訪問下一個節 點,一直訪問到需要的位置。而插入一個節點,對於單向鏈表,我們只提供在鏈表頭插入,只需要將當 前插入的節點設置爲頭節點,next指向原頭節點即可。刪除一個節點,我們將該節點的上一個節點的 next指向該節點的下一個節點
在表頭增加節點:
在表頭刪除節點:
/**
* @author shihaowei
* @date 2020-06-03 16:28
*/
public class SingleLinkList {
private int size;
private Node head;
public SingleLinkList() {
this.size = 0;
this.head = null;
}
/** 內部類節點 */
public class Node {
private Object data;
private Node next;
public Node(Object data) {
this.data = data;
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
", next=" + next +
'}';
}
}
/** 添加節點 */
public Object addHead(Object obj){
Node newNode = new Node(obj);
if (size == 0){
head = newNode;
}else {
newNode.next = head;
head = newNode;
}
size++;
return obj;
}
/** 刪除頭節點 */
public Object delHead(){
if (size > 0){
Object data = head.data;
head = head.next;
size--;
return data;
}
return false;
}
/** 獲取指定數據節點*/
public Node find(Object value){
Node current = head;
int tempsize = size;
while (tempsize>0){
if (current.data.equals(value)){
return current;
}else {
current = current.next;
tempsize--;
}
}
return null;
}
/** 刪除數據節點*/
public boolean delValue(Object value){
if (size == 0){
return false;
}
Node current = head;
Node prenode = head;
while (!current.data.equals(value)){
if (current.next == null){
return false;
}else {
prenode = current;
current = current.next;
}
}
//如果第一個節點就是要刪除的
if (current == head){
head = current.next;
size--;
}else {
prenode.next = current.next;
size--;
}
return true;
}
@Override
public String toString() {
return "SingleLinkList{" +
"size=" + size +
", head=" + head +
'}';
}
public static void main(String[] args) {
SingleLinkList linkList = new SingleLinkList();
linkList.addHead("a");
linkList.addHead("b");
linkList.addHead("c");
//linkList.delHead();
System.out.println(linkList);
}
}
雙端鏈表
對於單項鍊表,我們如果想在尾部添加一個節點,那麼必須從頭部一直遍歷到尾部,找到尾節點,然後在尾節點後面插入一個節點。這樣操作很麻煩,如果我們在設計鏈表的時候多個對尾節點的引用,那麼會簡單很多。
public class DoublePointLinklist {
private Node head;
private Node tail;
private int size;
// 鏈表的節點類
private class Node{
private Object data;
private Node next;
public Node(Object data) {
this.data = data;
}
}
public DoublePointLinklist() {
this.head = null;
this.tail = null;
this.size = 0;
}
/**
* 頭節點插入
* @param data
* @return
*/
public Object addHead(Object data){
Node node = new Node(data);
if (head.next == null){
head = node;
tail = node;
}else {
head.next = head;
head = node;
}
size++;
return data;
}
/**
* 尾節點插入
* @param data
* @return
*/
public Object addTail(Object data){
Node node = new Node(data);
if (head.next == null) {
head = node;
tail = node;
}else {
tail.next = tail;
tail = node;
}
size++;
return data;
}
/**
* 刪除節點
* @param data
* @return
*/
public boolean delNode(Object data){
if ( size == 0){
return false;
}
Node prenode = head;
Node current = head;
while (!head.data.equals(data)){
if (current.next == null){
return false;
}else {
prenode = current;
current = current.next;
}
}
if (current == head){
head = current.next;
}else {
prenode.next = current.next;
}
size--;
return true;
}
}
雙向鏈表
我們知道單向鏈表只能從一個方向遍歷,那麼雙向鏈表它可以從兩個方向遍歷
package person.shw.datastructure.link;
/**
* @author shihaowei
* @date 2020-06-08 16:39
*/
public class TwoWayLinkedList {
private Node head;
private Node tail;
private int size;
/**
* 節點類
*/
private class Node{
private Object object;
private Node next;
private Node pre;
public Node(Object object) {
this.object = object;
}
}
public TwoWayLinkedList() {
size = 0;
head = null;
tail = null;
}
/**
* 鏈表頭增加節點
* @param obj
*/
public void addHead(Object obj){
Node node = new Node(obj);
if (size == 0){
head = node;
tail = node;
}else {
head.pre = node;
head.next = head;
head = node;
}
size++;
}
/**
* 鏈表尾添加節點
* @param obj
*/
public void addTail(Object obj){
Node node = new Node(obj);
if (size == 0) {
head = node;
tail = node;
}else {
tail.next = node;
node.pre = tail;
tail = node;
}
size++;
}
/**
* 刪除頭節點
* @return
*/
public Object deleteHead(){
Node temp = head;
if (size != 0){
head = head.next;
head.pre = null;
size --;
}
return temp;
}
/**
* 刪除尾節點
* @return
*/
public Object deleteTail(){
Node temp = tail;
if (size != 0){
tail = tail.pre;
size --;
}
return temp;
}
/**
* 獲取節點個數
* @return
*/
public int getSize(){
return size;
}
/**
* 顯示節點信息
*/
public void display(){
if(size >0){
Node node = head;
int tempSize = size;
if(tempSize == 1){//當前鏈表只有一個節點
System.out.println("["+node.object+"]");
return; }
while(tempSize>0){
if(node.equals(head)){
System.out.print("["+node.object+"->");
}else if(node.next == null){
System.out.print(node.object+"]");
}else{
System.out.print(node.object+"->");
}
node = node.next;
tempSize--;
}
System.out.println(); }else{//如果鏈表一個節點都沒有,直接打印[]
System.out.println("[]");
}
}
}
有序鏈表
前面的鏈表實現插入數據都是無序的,在有些應用中需要鏈表中的數據有序,這稱爲有序鏈表。
在有序鏈表中,數據是按照關鍵值有序排列的。一般在大多數需要使用有序數組的場合也可以使用有序鏈表。有序鏈表優於有序數組的地方是插入的速度(因爲元素不需要移動),另外鏈表可以擴展到全部有效的使用內存,而數組只能侷限於一個固定的大小中。
public class OrderLinkedList {
private Node head;
private class Node{
private int data;
private Node next;
public Node(int data){
this.data = data;
}
}
public OrderLinkedList(){
head = null;
}
//插入節點,並按照從小打到的順序排列 public void insert(int value){
Node node = new Node(value);
Node pre = null;
Node current = head;
while(current != null && value > current.data){
pre = current;
current = current.next;
}
if(pre == null){
head = node;
head.next = current;
}else{
pre.next = node;
node.next = current;
}
}
//刪除頭節點
public void deleteHead(){
head = head.next;
}
public void display(){
Node current = head;
while(current != null){
System.out.print(current.data+" ");
current = current.next;
}
System.out.println("");
}
}
在有序鏈表中插入和刪除某一項最多需要O(N)次比較,平均需要O(N/2)次,因爲必須沿着鏈表上一 步一步走才能找到正確的插入位置,然而可以最快速度刪除最值,因爲只需要刪除表頭即可,如果一個 應用需要頻繁的存取最小值,且不需要快速的插入,那麼有序鏈表是一個比較好的選擇方案。比如優先 級隊列可以使用有序鏈表來實現
總結
上面我們講了各種鏈表,每個鏈表都包括一個LinikedList對象和許多Node對象,LinkedList對象通常 包含頭和尾節點的引用,分別指向鏈表的第一個節點和最後一個節點。而每個節點對象通常包含數據部 分data,以及對上一個節點的引用prev和下一個節點的引用next,只有下一個節點的引用稱爲單向鏈 表,兩個都有的稱爲雙向鏈表。next值爲null則說明是鏈表的結尾,如果想找到某個節點,我們必須從 第一個節點開始遍歷,不斷通過next找到下一個節點,直到找到所需要的。棧和隊列都是ADT,可以用 數組來實現,也可以用鏈表實現