1.鏈表和數組的比較
1).數組作爲數據存儲的一種結構,有一定的缺陷,比如無序數組搜索效率低,有序數組插入效率低,兩者刪除效率都比較低。而且在創建數組的時候需要指定數組的大小,如果無法提前預知大小,數組的動態擴展也是件麻煩事(netty的bytebuf是動態擴展的數組,有時間可以看看怎麼實現的),給的值足夠大的話,會造成不必要的內存開銷。那麼鏈表呢,可以有效解決擴容的問題。
2).數組可以用來實現棧、隊列(例如PriorityBlokingQueue內部用的就是一個Object[])等其他數據結構,是一種通用數據結構。鏈表也是一種通用數據結構,同樣也可以實現棧和隊列。
3).頻繁的通過下標來訪問數據,顯然數組的優勢更好
2.鏈表的種類
單向鏈表、雙端鏈表、有序鏈表、雙向鏈表、循環鏈表。我們就每種鏈表研究下
3.鏈表定義
鏈表(Linked List):一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是通過鏈表中的指針連接次序決定的。鏈表由一系列結點組成,結點可以在運行時動態生成。結點由兩部分組成:一個是存儲數據的數據域,一個是存儲下一個結點的指針域。
4.單向鏈表(Single Linked List)
單向鏈表的結點包含兩部分:一部分是存儲數據,一部分是存儲下一個結點的位置;最後一個結點存儲的位置是null。單向鏈表的結構導致只能從一個方向開始遍歷,查找一個結點,一般從第一個結點開始向下搜尋,直到搜尋到想要的結點;刪除一個結點,先找到這個結點的上一個結點和下一個結點,然後將該結點的上一下結點指向該節點的下一個節點;增加一個結點,一般只提供在表頭插入(在尾部插入也是可以實現,不過需要從頭開始遍歷結點,找到尾部結點,然後該尾部結點指向新的結點。在其他位置插入,因爲沒有下標,無法指定位置,只能是一定規則隨機插入)
在表頭增加節點:
刪除節點:
4.1單向鏈表的代碼實現:
public class SingleLinkedList {
private int size;//鏈表節點的個數
private Node head;//頭節點
public SingleLinkedList(){
size = 0;
head = null;
}
//鏈表的每個節點類
private class Node{
private Object data;//每個節點的數據
private Node next;//每個節點指向下一個節點的連接
public Node(Object data){
this.data = data;
}
}
//在鏈表頭添加元素
public Object addHead(Object obj){
Node newHead = new Node(obj);
if(size == 0){
head = newHead;
}else{
newHead.next = head;
head = newHead;
}
size++;
return obj;
}
//在鏈表頭刪除元素
public Object deleteHead(){
Object obj = head.data;
head = head.next;
size--;
return obj;
}
//查找指定元素,找到了返回節點Node,找不到返回null
public Node find(Object obj){
Node current = head;
int tempSize = size;
while(tempSize > 0){
if(obj.equals(current.data)){
return current;
}else{
current = current.next;
}
tempSize--;
}
return null;
}
//刪除指定的元素,刪除成功返回true
public boolean delete(Object value){
if(size == 0){
return false;
}
Node current = head;
Node previous = head;
while(current.data != value){
if(current.next == null){
return false;
}else{
previous = current;
current = current.next;
}
}
//如果刪除的節點是第一個節點
if(current == head){
head = current.next;
size--;
}else{//刪除的節點不是第一個節點
previous.next = current.next;
size--;
}
return true;
}
//判斷鏈表是否爲空
public boolean isEmpty(){
return (size == 0);
}
//顯示節點信息
public void display(){
if(size >0){
Node node = head;
int tempSize = size;
if(tempSize == 1){//當前鏈表只有一個節點
System.out.println("["+node.data+"]");
return;
}
while(tempSize>0){
if(node.equals(head)){
System.out.print("["+node.data+"->");
}else if(node.next == null){
System.out.print(node.data+"]");
}else{
System.out.print(node.data+"->");
}
node = node.next;
tempSize--;
}
System.out.println();
}else{//如果鏈表一個節點都沒有,直接打印[]
System.out.println("[]");
}
}
}
測試:
@Test
public void testSingleLinkedList(){
SingleLinkedList singleList = new SingleLinkedList();
singleList.addHead("A");
singleList.addHead("B");
singleList.addHead("C");
singleList.addHead("D");
//打印當前鏈表信息
singleList.display();
//刪除C
singleList.delete("C");
singleList.display();
//查找B
System.out.println(singleList.find("B"));
}
打印結果:
4.2單向鏈表實現棧
棧的pop()方法和push()方法,對應於鏈表的在頭部刪除元素deleteHead()以及在頭部增加元素addHead()。
public class StackSingleLink {
private SingleLinkedList link;
public StackSingleLink(){
link = new SingleLinkedList();
}
//添加元素
public void push(Object obj){
link.addHead(obj);
}
//移除棧頂元素
public Object pop(){
Object obj = link.deleteHead();
return obj;
}
//判斷是否爲空
public boolean isEmpty(){
return link.isEmpty();
}
//打印棧內元素信息
public void display(){
link.display();
}
}
5.雙端鏈表
單向鏈表,如果我們想要在尾部插入一個結點,那麼只能從頭部結點開始遍歷,找到最後一個結點,然後尾部結點指向新的結點,我們發現,這樣性能會很低。如果我們在設計鏈表的時候多一個對尾部結點的引用,向上面的操作,會變得特別簡單。
5.1雙端鏈表的代碼實現:
public class DoublePointLinkedList {
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 DoublePointLinkedList(){
size = 0;
head = null;
tail = null;
}
//鏈表頭新增節點
public void addHead(Object data){
Node node = new Node(data);
if(size == 0){//如果鏈表爲空,那麼頭節點和尾節點都是該新增節點
head = node;
tail = node;
size++;
}else{
node.next = head;
head = node;
size++;
}
}
//鏈表尾新增節點
public void addTail(Object data){
Node node = new Node(data);
if(size == 0){//如果鏈表爲空,那麼頭節點和尾節點都是該新增節點
head = node;
tail = node;
size++;
}else{
tail.next = node;
tail = node;
size++;
}
}
//刪除頭部節點,成功返回true,失敗返回false
public boolean deleteHead(){
if(size == 0){//當前鏈表節點數爲0
return false;
}
if(head.next == null){//當前鏈表節點數爲1
head = null;
tail = null;
}else{
head = head.next;
}
size--;
return true;
}
//判斷是否爲空
public boolean isEmpty(){
return (size ==0);
}
//獲得鏈表的節點個數
public int getSize(){
return size;
}
//顯示節點信息
public void display(){
if(size >0){
Node node = head;
int tempSize = size;
if(tempSize == 1){//當前鏈表只有一個節點
System.out.println("["+node.data+"]");
return;
}
while(tempSize>0){
if(node.equals(head)){
System.out.print("["+node.data+"->");
}else if(node.next == null){
System.out.print(node.data+"]");
}else{
System.out.print(node.data+"->");
}
node = node.next;
tempSize--;
}
System.out.println();
}else{//如果鏈表一個節點都沒有,直接打印[]
System.out.println("[]");
}
}
}
6.雙向鏈表
單向鏈表只能從一個方便遍歷,那麼雙向鏈表可以從兩個方向遍歷
6.1雙向鏈表的代碼實現:
public class TwoWayLinkedList {
private Node head;//表示鏈表頭
private Node tail;//表示鏈表尾
private int size;//表示鏈表的節點個數
private class Node{
private Object data;
private Node next;
private Node prev;
public Node(Object data){
this.data = data;
}
}
public TwoWayLinkedList(){
size = 0;
head = null;
tail = null;
}
//在鏈表頭增加節點
public void addHead(Object value){
Node newNode = new Node(value);
if(size == 0){
head = newNode;
tail = newNode;
size++;
}else{
head.prev = newNode;
newNode.next = head;
head = newNode;
size++;
}
}
//在鏈表尾增加節點
public void addTail(Object value){
Node newNode = new Node(value);
if(size == 0){
head = newNode;
tail = newNode;
size++;
}else{
newNode.prev = tail;
tail.next = newNode;
tail = newNode;
size++;
}
}
//刪除鏈表頭
public Node deleteHead(){
Node temp = head;
if(size != 0){
head = head.next;
head.prev = null;
size--;
}
return temp;
}
//刪除鏈表尾
public Node deleteTail(){
Node temp = tail;
if(size != 0){
tail = tail.prev;
tail.next = null;
size--;
}
return temp;
}
//獲得鏈表的節點個數
public int getSize(){
return size;
}
//判斷鏈表是否爲空
public boolean isEmpty(){
return (size == 0);
}
//顯示節點信息
public void display(){
if(size >0){
Node node = head;
int tempSize = size;
if(tempSize == 1){//當前鏈表只有一個節點
System.out.println("["+node.data+"]");
return;
}
while(tempSize>0){
if(node.equals(head)){
System.out.print("["+node.data+"->");
}else if(node.next == null){
System.out.print(node.data+"]");
}else{
System.out.print(node.data+"->");
}
node = node.next;
tempSize--;
}
System.out.println();
}else{//如果鏈表一個節點都沒有,直接打印[]
System.out.println("[]");
}
}
}
可以發現相對於單向鏈表,不僅擁有單向鏈表的功能,還簡化和擴展了其他操作