數據結構之隊列——輸出楊輝三角形

定義

隊列是一種操做受限的線性表,只容許在一端進行插入,另外一端進行刪除。插入的一端稱爲隊尾,刪除的一端稱爲隊頭,因爲這樣的限制,因此隊列具備先進先出的特性,所以隊列也是一種先進先出的線性表。算法

順序存儲

隊列的順序存儲結構,除了存儲的數組,還須要一個隊尾指針(rear),和隊頭指針(front),初始化隊列的時候,rear和front都指向同一個下標0,這shi隊爲空。數組

  • 當一個元素入隊,隊尾指針+1
  • 當一個元素出隊,隊頭指針+1
  • 在非空隊列中,隊頭指針始終指向對頭元素,而隊尾指針始終指向隊尾元素的下一個位置

在這種狀況下,會出現假溢出現象,由於入隊和出隊操做中,頭,尾指針都只增長,不減小,致使被刪除的元素空間永遠沒法從新利用。即儘管隊列中的元素個數遠遠小於數組的大小,但因爲尾指針已經超出數組的上界,致使不能進行入隊操做,這種現象稱爲假溢出ui

數組大小爲4,隊列操做時,頭、尾指針變化過程以下圖 spa

循環隊列

爲了克服假溢出,能夠怎麼改進呢?很天然的就想到,把新的元素放到空餘的空間裏,即又回到數組下標0位置處,這樣看上去就像一個首尾相接的圓環,這種隊列稱爲循環隊列指針

循環隊列的出隊和入隊,仍然是頭,尾指針加一,只不過當頭,尾指針到達數組上界時,加1操做,又回到了下界0處。code

// i 表明頭,尾指針
if i+1 == maxSize {
    i = 0
} else {
    i++
}
複製代碼

上述的這種操做,可使用求模運算簡化,即i = (i+1) % maxSize,這樣就能充分利用數組上的全部空間,除非數組空間被佔滿,不然不會形成溢出。 來看下循環隊列,頭尾指針的變化過程。 orm

從中會發現一個問題,當front == rear的時候,有多是隊空,也多是隊滿。爲何會出現這個問題呢?

咱們以隊列最大容量爲4,來分析這個問題:cdn

  • 首先,是以front,rear的相對位置來判斷隊列的狀態,那麼front,rear的相對位置有:0,1,2,3四種狀況
  • 實際上,隊列裏的狀態有:0(空隊列),1個元素,2個元素,3個元素,4個元素(隊滿)五種狀況
  • so,要用4種狀況來區分5種狀況,很顯然,不合實際

怎麼解決呢?通常都兩種方案:blog

一、使用額外的標誌位tag,當入隊的時候,把tag設置成1,當出隊的時候,把tag設置成0,那麼當front==rear時,就能夠經過tag的值來判斷是空隊,仍是滿隊隊列

二、少用一個空間,即數組最大容量爲4,但咱們只用3個容量,這樣判斷空隊列仍然是front==rear,而判斷隊列是否滿,則就變成(rear+1)%maxSize == front,則爲滿。(下面的實例代碼,以此方案實現)即以下圖

固然,也可使用鏈式存儲的方式來構建隊列,若是使用鏈式,就不存在容量的問題,這樣也就不須要判斷隊滿。

主要操做

結構描述

type data interface{}

type Queue struct {
	list    []data
	front   int // 頭指針
	rear    int // 尾指針
	maxSize int // 最大容量
}

func New(maxSize int) *Queue {
	q := &Queue{
		list:    make([]data, maxSize+1),
		front:   0,
		rear:    0,
		maxSize: maxSize + 1, // 空餘一個容量不使用
	}
	return q
}
複製代碼

判滿

func (q *Queue) IsFull() bool {
	return (q.rear + 1) % q.maxSize == q.front
}
複製代碼

判空

func (q *Queue) IsEmpty() bool {
	return q.front == q.rear
}
複製代碼

入隊

判斷隊是否已經滿,滿就報錯,不然入隊

func (q *Queue) Enqueue(value data) (bool, error) {
	if q.IsFull() {
		return false, errors.New("隊已滿")
	}
	q.list[q.rear] = value
	q.rear = (q.rear + 1) % q.maxSize
	return true, nil
}
複製代碼

出隊

隊爲空,則報錯,不然出隊

func (q *Queue) Dequeue() (data, error) {
	if q.IsEmpty() {
		return nil, errors.New("隊爲空")
	}
	value := q.list[q.front]
	q.list[q.front] = nil
	q.front = (q.front + 1) % q.maxSize
	return value, nil
}
複製代碼

獲取隊頭的值

func (q *Queue) GetHead() (data, error) {
	if q.IsEmpty() {
		return nil, errors.New("隊爲空")
	}
	return q.list[q.front], nil
}
複製代碼

應用:輸出楊輝三角形

楊輝三角,是二項式係數在三角形中的一種幾何排列,以下圖

它有兩個比較顯著的特色:

  • 每行最兩端的值爲1
  • 每一個數字等於上一行的左右兩個數字之和。可用此性質寫出整個楊輝三角,即第n+1行的第i個數等於第n行的第i-1個數和第i個數之和,即 C(n+1,i)=C(n,i)+C(n,i-1)。

基於以上的性質,使用程序輸入楊輝三角的時候,一種想法就是,利用兩個數組,在輸出當前行的時候,就計算下一行的值,放到另外一個數組裏,兩個數組交替使用。

第二種方案,咱們能夠利用隊列來輸出,在空間上能夠減小一個數組,在使用隊列輸出的楊輝三角的時候,有一個小技巧,就是在每行的兩端添加兩個0,即成以下的形式

0 1 0
 0 1 1 0
0 1 2 1 0 
複製代碼

在這個前提下,算法思路(n表明行數):

一、初始化一個隊列,將第一列 0 1 0 依次入隊;

二、此時每一行的元素個數爲n + 2,依次出隊並輸出該行的每個元素,0出隊但不輸出;

三、在元素出隊的同時,計算下一行對應位置的數值,即出隊元素 + 新的隊頭元素,並把計算獲得的值入隊

四、當該行的每個元素都輸出完了,隊列裏也就計算好了下一行的元素,此時再把0入隊,這個0便是這一行結束的0,也是下一行開始的0

五、重複2,3,4直到n結束。

咱們以 n = 4爲例,看看隊列裏元素的變化:

const maxSize = 1000

func printYangHui(n int) {
	q := Queue.New(maxSize)
	q.Enqueue(0)
	q.Enqueue(1)
	q.Enqueue(0)
	for i := 1; i <= n; i++ {
		formatPrint(n-i) // 格式化輸出
		for j := 1; j < i+2; j++ {
			// 第i行,在隊列中有i + 2個數字,包括頭尾兩個0,
			// 0 1 0
			// 0 1 1 0
			// 0 1 2 1 0
			s, _ := q.Dequeue()
			if s != 0 {
				fmt.Printf(" %d", s)
			}
			t, _ := q.GetHead()
			q.Enqueue(s.(int) + t.(int)) // 下一行中的數字就是其左右肩之和
		}
		q.Enqueue(0) // 再把每行的0入隊
		fmt.Println()
	}
}

printYangHui(4) // 結果以下圖
複製代碼

總結

以上的應用只是隊列的一個小應用,理論上數據流符合先進先出的規則,均可以考慮使用隊列解決問題。好比打印機的打印調度,先進的內容,會被先打印出來,等等。

Thanks!

相關文章
相關標籤/搜索