Java中的數據結構又很多種,如棧,隊列,樹等,但是他們的形式歸根到底就是兩種:一個是數組,一個是鏈表,所有的結構都是對這兩個的變形。
數組和鏈表都屬於是線性表,那什麼是線性表:一個線性表是n個相同特性的數據元素的有序序列。各元素之間是一對一的關係。但這並不是說一個線性表除了第一個和最後一個其他都是首尾相接,因爲循環鏈表也是一種線性表。因爲這個線性關係針對的是邏輯上的。
數組,是物理上連續的一塊存儲空間,它便於根據索引直接取出數據和存入數據,但是搜索和查找的效率低下,插入和刪除的效率低下,並且數組一旦創建以後大小就固定,如果存儲的數據太少,空間就浪費,如果存儲的數據太多,那就不能滿足,於是有了鏈表的產生。
鏈表不能解決數據存儲的所有問題,一般需要頻繁使用下標查找訪問數據的地方都不使用鏈表。鏈表在內存中存儲的位置是隨機地,但是通過指針相互連接,形成一種線性關係,所以鏈表是一種線性表。
對於鏈表,無論是哪種形式,我們希望能實現這些基礎功能:
//創建結點類
//添加節點(只能頭部插入)
//查找數據
//刪除數據
//打印數據
//計算長度
1.單向鏈表
一個單向鏈表的結點分成兩部分:數據+下一結點的地址。
特性:只能單向遍歷,只提供鏈表頭插入
單向鏈表的具體實現:
package com.lm.list1118;
public class SingleLinklist {
//創建結點類
public class Node{
Object data;//存儲數據
Node next;//指向下一節點的地址
public Node(Object data){//構造函數
this.data = data;
}
}
//創建頭結點
private Node head;
//添加節點(只能頭部插入)
public void addNode(Object data) {
Node node = new Node(data);
if(head == null) {//當鏈表爲空時
head = node;
}else {
node.next = head;
head = node;
}
}
//查找指定數據
public Node findNode(Object data) {
Node tmp = head;
while(tmp != null) {
if(data.equals(tmp.data)) {
return tmp;
}
tmp = tmp.next;
}
return null;
}
//刪除數據(刪除頭結點)
public boolean deleteHead() {
while(head.next != null) {
head = head.next;
return true;
}
return false;
}
//刪除數據(刪除指定元素)
public boolean deleteNode(Object data) {
if(data.equals(head.data)) {
head = head.next;
return true;
}
Node tmp = head.next;
Node curNode = head;
while(tmp != null) {
if(data.equals(tmp.data)) {
curNode.next = tmp.next;
return true;
}else {
curNode = curNode.next;
tmp = tmp.next;
}
}
return false;
}
//打印數據
public void printNode() {
Node node = head;
while(node != null) {
System.out.println("元素:"+node.data);
node = node.next;
}
}
//計算長度
public int length() {
int len = 0;
Node node = head;
while(node != null) {
len++;
node = node.next;
}
return len;
}
public static void main(String[] args) {
SingleLinklist sl = new SingleLinklist();
sl.addNode(10);
sl.addNode(7);
sl.addNode(4);
sl.addNode(5);
System.out.println("生成的鏈表爲:");
sl.printNode();
sl.deleteHead();
System.out.println("刪除頭結點後鏈表爲:");
sl.printNode();
sl.deleteNode(7);
System.out.println("刪除數據4後鏈表爲:");
sl.printNode();
System.out.println("元素8是否存在?"+sl.findNode(8));
}
}
上面的單向鏈表我將完整的代碼貼出,之後的只需要稍加改動。
2.雙端鏈表
對於單向鏈表,我們暫定是對頭結點部位進行插入操作,但是這遠遠是不能滿足我們的需求,如果想要在任意位置插入數據,或者直接在尾部插入,那麼我們必須從頭部一直遍歷到尾部,這樣的算法複雜度是O(n),如果我們適當的增加一個尾指針,用於指向尾結點,那麼複雜度會直接降到O(1)。
雙端鏈表的具體實現:
//尾部添加節點
public void addTail(Object data) {
Node node = new Node(data);
if(tail == null) {
head = node;
tail = node;
}
tail.next = node;
tail = node;
}
基於雙端鏈表實現隊列:
package com.lm.list1118;
/**
* 雙端鏈表實現隊列
* 先進先出(隊首出,隊尾進)
* @author Administrator
*
*/
public class QueueLinklist {
private DoublePointLinklist dl;
public QueueLinklist(){
dl = new DoublePointLinklist();//創建雙端鏈表對象
}
//入隊列
public void insert(Object data) {
dl.addTail(data);
}
//出隊列
public void delete() {
dl.deleteHead();
}
//獲取隊列長度
public int length(){
return dl.length();
}
//判斷是否爲空
public boolean isEmpty() {
if(dl.length()==0)
return true;
return false;
}
//顯示隊列元素
public void print() {
dl.printNode();
}
}
3.有序鏈表
一般說到鏈表,我們都認爲他是無序的,這樣就不方便查找最大值或最小值,所以可以對鏈表創建的時候進行排序,形成有序鏈表,這樣可以通過O(1)的時間複雜度獲取最值和刪除最值(刪除表頭即可)。而且有序鏈表在插入的時候比有序數組速度更快,不需要進行元素的移動,而且不受固定大小的限制,所以有序鏈表有時候可以代替有序數組。
有序鏈表的具體實現:
插入函數:
4.有序鏈表優化無序數組的排序
我們知道冒泡排序,選擇排序和插入排序需要的時間複雜度是O(N2),如果我們將無序的數組一個個取出然後插入有序鏈表中進行排序,然後再將排完的鏈表中的數據一個個取出重新放進數組,這個實現的排序。大概進行N2/4次比較,優化了效率。但是不足的地方就是開闢了需要的兩倍的空間。
//插入結點,從小到大排列
public void insert(int data) {
Node node = new Node(data);
Node pre = null;
Node cur = head;
//我本來想着的是用兩個變量,一個表示當前指針,一個表示當前指針的下一個指針,但是邏輯上容易出現空指針的情況,情況太多,考慮不全
//但是如果將一個變量表示成當前指針的前一個就簡單多了,因爲如果當前指針不爲空,那麼前一個指針一定不爲空
while(cur != null && cur.data<data) {
pre = cur;
cur = cur.next;
}
if(pre == null) {//當鏈表爲空時
head = node;
head.next = cur;
}else {//當鏈表不爲空,並且找到插入的位置
pre.next = node;
node.next = cur;
}
}
##5. 雙向鏈表
所謂的雙向鏈表,就是相對單向鏈表而言的,可以兩個方向遍歷。
借用網上的圖來說明一下雙向鏈表的插入和刪除操作
具體的代碼實現:
/
/表頭增加節點
public void addHead(Object data) {
Node node = new Node(data);
if(size==0) {
head = node;
tail = node;
}else {
head.prev = node;//不要忘記對prev指針的操作
node.next = head;
head = node;
}
size++;
}
//表尾增加節點
public void addTail(Object data) {
Node node = new Node(data);
if(size == 0) {
head = node;
tail = node;
}else {
tail.prev = node;
node.next = tail;
tail = node;
}
size++;
}
//刪除表頭
public void deleteHead() {
if(size != 0) {
head = head.next;
head.prev = null;
size--;
}
}
//刪除表尾
public void deleteTail() {
if(size!=0) {
tail = tail.prev;
tail.next = null;
size--;
}
}