數據結構與算法(4) -- list、queue以及stack


今天主要給大家介紹幾種數據結構,這幾種數據結構在實現原理上較為類似,我習慣稱之為類list的容器。具體有list、stack以及queue。

list的節點Node

首先介紹下node,也就是組成list的節點。從面向對象的角度來說節點也是就一個類,list里面包含了node對象的實例,以及操作/管理這些實例的方法。先給出一個粗糙的node的C++代碼如下代碼塊所示。可以看出除了保有當前節點的信息,其還有指向前一個節點和后一個節點的指針。如果單獨使用當然另外還需要相應的構造函數。如果是使用node組成其他的如list數據結構,也可以不需要node自己的構造函數,而完全交由list分配空間和數據,這部分可以參考《STL源碼剖析》。好吧,扯得有點遠了,為了講述的簡單,后面的代碼都將采取簡易的實現方式,如有不妥歡迎私信。

template<typename T>
struct Node
{
	typedef void *  void_pointer;
	void_pointer prev;
	void_pointer next;
	T data;

	Node(T data){
		this->data = data;
		prev = nullptr;
		next = nullptr;
	}
};

Node構成的數據結構有哪些

說完了node,那么由node(或其變體)作為基本單元的數據結構有哪些呢?當然首先有list(鏈表),其次stack(棧)以及queue(隊列)也是哈。list是雙向鏈接的,頭和尾都可以插入和刪除node,當然中間插入刪除也是可以的。而stack則是先進后出的隊列,啥意思呢,也就是說:插入和刪除都是針對最頂層的node的;queue則不同,它是先進先出的,和我們日常排隊買東西是一樣的原理。接下來分別介紹。

list

先來個圖感性認識一下:

其實看了圖之后也就無需多言了哈,不就是node一個鏈接一個么。嘿,簡單!!!

stack

那stack呢,更加簡單啦。只要將雙向的鏈表換成單向的鏈表就可以實現了哈。在表的前端插入來實施push操作,通過刪除表的前端元素來完成pop操作。top操作知識考察表前端元素並返回它的值。(當然stack也可以用基於vector來實現)。

queue

先進先出,其實現可以基於鏈表。但是也可以基於deque/vector。與stack一樣若基於已有的結構,其實現是較為簡單的,這里不詳細敘述。

實現一個list

其實從stl的源碼可以看出,要真正實現一個list並不像看上去那么簡單。考慮到內存分配以及構造的細節,還是比較麻煩的。這里我們主要是為了掌握list的基本結構,對其主要操作做一個了解。所以不必寫那么復雜的實現(哈哈,太復雜的我也寫不好,還是建議閱讀源碼哈)。

list的迭代器

迭代器是后需幾乎所有操作的基礎。那么怎么組織這個迭代器呢,或者換句換說,迭代器在哪里定義呢?我們可以這么組織我們的代碼:

using namespace std;

template <typename Object>
class List
{
  private:    
    struct Node
    {
		...
    };

  public:
    class const_iterator
    {
      public:
		...
      protected:
        Node *current;
		...
        friend class List<Object>;
    };

    class iterator : public const_iterator
    {
      public:
  		...
        friend class List<Object>;
    };

  public:
    List( )
      { init( ); }
    ~List( )
    //其他方法:
	...
	...
  private:
    int   theSize;
    Node *head;
    Node *tail;

    void init( )
    {
        theSize = 0;
        head = new Node;
        tail = new Node;
        head->next = tail;
        tail->prev = head;
    }
};

稍微分析一下,首先list由node一個鏈接一個構成,所以需要一個node類。另外不希望第三方類使用,所以是私有的。在看迭代器,迭代器是為了方便遍歷list或者指代list的當前位置的。所以其本質是一個指針,而且是指向node的指針,所以可以看到const_iterator類有個指針的定義:Node *current;。因為有時候通過迭代器只是想單純訪問數據而已,而有時候是希望能夠改變node節點的數據。所以就有了const_iterator以及iterator兩套哈。我不打算在這里給出迭代器的實現細節,這部分內容有很多教材的源碼都是不錯的,STL源碼可能復雜些不建議。推薦《數據結構與算法分析--C++語言描述(第四版)》,請參考我的GitHub。但是還是有幾個細節需要提一下:

  • 迭代器的類要是list的友元。
  • 迭代器在某種意義上就是某種智能指針,所以重載*以及->(或者++, --)操作是必須的。
  • 為了后續操作的方便,在list中==以及!=通常也少不了。
  • 通常需要head(表示第一個元素的前一個位置)以及tail(最后一個元素的最后一個位置)。這是為了在遍歷的時候可以用迭代器!=head以及tail來判斷是否到達邊界。

幾個list重要操作的原理與實現

現在假設我們已經有了一個功能完整的迭代器,那么接下來的操作基於迭代器就好了。

刪除

先給出圖和代碼:

iterator erase( iterator itr )
    {
        Node *p = itr.current;
        iterator retVal( p->next );
        p->prev->next = p->next;
        p->next->prev = p->prev;
        delete p;
        --theSize;

        return retVal;
    }

	iterator erase( iterator from, iterator to )
    {
        for( iterator itr = from; itr != to; )
            itr = erase( itr );

        return to;
    }

直觀上看我們只要刪除1和2號線,增加3和4號線就可以了。代碼就是上面的代碼哈,很簡單。但是,經常容易犯得錯誤是:忘記先保存刪除節點的位置。那么在1與2號線刪除之后就再也無法釋放其資源了哈,這就是第一行代碼 Node *p = itr.current; 的用意。第二行代碼 iterator retVal( p->next ); 是為了刪除之后仍然能夠得到有效的迭代器。

插入

插入操作也是類型的,只不過這里用了一個較為看上去復雜的操作 p->prev = p->prev->next = new Node{ x, p->prev, p } 。記住,c++賦值語句,是從右往左執行的。結合圖,也就不難理解這句話了。

// Insert x before itr.
    iterator insert( iterator itr, const Object & x )
    {
        Node *p = itr.current;
        ++theSize;
        return iterator( p->prev = p->prev->next = new Node{ x, p->prev, p } );
    }

    // Insert x before itr.
    iterator insert( iterator itr, Object && x )
    {
        Node *p = itr.current;
        ++theSize;
        return iterator( p->prev = p->prev->next = new Node{ std::move( x ), p->prev, p } );
    }

其他--stl/java

stl里list的實現實際上稍有出入,主要體現在內存分配與構造。實在是能力有限,在這里不敢瞎說。推薦大家閱讀侯捷老師的《STL源碼剖析》。另外java里的迭代器需要實現Iterator接口就好了。

關於queue與stack的基於node的實現就不贅述了。另外dequeue也可基於node實現,但是stl里是基於分段連續線性空間實現的,如果有可能會在后續的博客中詳細講述。

最后給一個我之前寫的dequeue-java版本作為結束。

import java.util.Iterator;
import java.util.NoSuchElementException;

public class Deque<Item> implements Iterable<Item> {
	private Node first = null;
	private Node last = null;
	private int size = 0;
	
	public Deque() {
		
	}
	
	private class Node{
		Item item;
		Node next;
		Node previous;
	}
	
	public boolean isEmpty() {
		return first==null && last==null;
	}
	
	public int size() {
		return size;
	}
	
	public void addFirst(Item item) {
		if(item==null)
			throw new IllegalArgumentException();
		if(0==size) {
			//if size==0, let first and last point to item
			first = new Node();
			first.item = item;
			last = first;
		}else {
			Node oldfirst = first;
			first = new Node();
			first.item = item;
			first.next = oldfirst;
			oldfirst.previous = first;
		}
		size++;
	}
	
	public void addLast(Item item) {
		if(item==null)
			throw new IllegalArgumentException();
		if(0==size) {
			//if size==0, let first and last point to item
			last = new Node();
			last.item = item;
			first = last;
		}else {
			Node oldlast = last;
			last = new Node();
			last.item = item;
			last.previous = oldlast;
			oldlast.next = last;
		}
		size++;
	}
	
	public Item removeFirst() {
		if(size==0)
			throw new NoSuchElementException();
		Item itemtem = first.item;
		if(size==1) {
			first = null;
			last = null;
		}else {
			Node oldfirst = first;
			first = oldfirst.next;
			first.previous = null;
			oldfirst.next = null;
			oldfirst.item = null;
		}
		size--;
		return itemtem;
	}
	
	public Item removeLast() {
		if(size==0)
			throw new NoSuchElementException();
		Item itemtem = last.item;
		if(size==1) {
			itemtem = last.item;
			first = null;
			last = null;
		}else {
			Node oldlast = last;
			last = oldlast.previous;
			last.next = null;
			oldlast.previous = null;
			oldlast.item = null;
		}
		size--;
		return itemtem;
	}
	
	private class DequeIterator implements Iterator<Item>{
		private Node current = first;
		@Override
		public boolean hasNext() {
			return current != null;
		}

		@Override
		public Item next() {
			if(!hasNext())
				throw new NoSuchElementException();
			Item itemtem = current.item;
			current = current.next;
			return itemtem;
		}
		
		public void remove() {
			throw new UnsupportedOperationException();
		}
	}
	
	public Iterator<Item> iterator(){
		return new DequeIterator();
	}
	
	
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Deque<Integer> deque = new Deque<Integer>();
		deque.addFirst(1);
		deque.addFirst(2);
		deque.addFirst(3);
		deque.addLast(4);
		deque.addLast(5);
		deque.addLast(6);
		deque.removeFirst();
		deque.removeLast();
		deque.removeFirst();
		deque.removeLast();
		deque.removeFirst();
		deque.removeLast();
		for (Integer integer : deque) {
			System.out.println(integer);
		}
	}

}

See you next time. Happy Coding!!!
我的github


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM