數據結構複習篇:線性表

數據結構與算法,是編寫高質量程序的內功之一,在打算步入社會尋找自己的第一份工作之際,我準備用一週左右的時間來複習一遍數據結構與算法。在任何時候重視基礎知識,都不是一件多餘的事。

複習用書:《數據結構與算法分析 C++版》第二版 [美] Clifford A.Shaffer著,張銘、劉曉丹 等譯 電子工業出版社

一、線性表

線性表是這樣一種表結構,它的元素element存放在一個連續的表中,這個表的關鍵之處就在於“連續”(注意,這裏僅是指邏輯上的連續,在線性表中,有一種鏈表實現,其內存不要求連續),就是說當一個元素添加進一個表,或一個元素從表中刪除的時候,表中元素的排列就像有一條線把它們串起來一樣。我相信這樣的解釋已經顯得囉嗦了,因爲線性表這個名字,本來就很形象。

從程序設計的角度,我們先定義用戶所關心的線性表的界面,就好像我們找女友的時候,先在心中描繪一個夢中情人的形象:

/*
定義線性表的界面,作爲順序表或鏈表的公用接口
*/
#ifndef LISTINTERFACE_H
#define LISTINTERFACE_H
template
<class T> class ListInterface
{
public:
    
//向線性表的柵欄的right partition的第一個位置插入一個元素,返回值表示插入是否成功
    virtual bool insert(const T&)=0;
    
//刪除柵欄的right partition的第一個位置的元素,返回值表示刪除是否成功,被刪除的值通過形參返回
    virtual bool remove(T&)=0;
    
//清空所有元素,但不銷燬內存
    virtual void clear()=0;
    
//還有其他接口函數,比較簡單,不作重點.
};

#endif

一般有兩種實現:

1、基於數組的順序表(Array_based List):訪問第i個元素的時間代價爲O(1),刪除或插入一個元素的代價爲O(n)。

2、鏈表(Linked List):訪問第i個元素的時候代價爲O(1),刪除或插入一個元素的代價爲O(n)。

由此可見,這兩種表各具特色。在應用中,應該考慮對錶中元素是訪問多,還是修改多。前者採用AList,後者採用LList。

以下是這兩種實現:(由於是複習,我只挑我認爲關鍵的寫出來。這對於找工作的人,應付筆試也許有一定幫助)

 

#include "ListInterface.h"
#include 
<iostream>
using namespace std;

//用數組實現線性表AList(array_based list)
template<class T> class AList
            :
public ListInterface<T>
{
private:    
    
int maxSize;    //順序表的最大長度
    int listSize;    //實際長度
    int fence;    //柵欄位置,後面將發現這與鏈表的柵欄是不同的
    T *listArray;    //一個指向順序表數組的指針
public:
    AList(
int size = 100)
    {
        listArray 
= new T[size];
        maxSize 
= size;
        fence 
= listSize = 0;
    }
    
~AList()
    {
        delete [] listArray; 
    }

    
bool insert(const T& element)
    {
        
if (listSize == maxSize)
        {
            
return false;    //表滿,無法再插入元素
        }
        
for (int i=listSize-1; i>=fence; i--)    //把柵欄右邊的所有元素往後挪一位
        {
            listArray[i
+1= listArray[i];
        }
        listArray[fence] 
= element;    //給插入的元素賦值
        ++listSize;
        
return true;
    }
    
    
bool remove(T& elemet)
    {
        
if (fence == listSize)
        {
            
return false;    //柵欄右邊沒有元素可以刪除
        }
        elemet 
= listArray[fence];    //把將要刪除的值傳給外面的參數
        for (int i=fence+1; i<listSize; i++)    //把柵欄右邊第二個及後面的元素往前挪一位
        {
            listArray[i
-1= listArray[i];
        }
        
--listSize;
        
return true;
    }

    
void clear()
    {
        delete [] listArray;
        listArray 
= new T[maxSize];
    }

};    

/*
用鏈表實現線性表,首先定義一個結點類型Node
其次,定義一個鏈表(Linked_List)LList
*/

template
<class T> class LList;    //注意,聲明友元模板時,必須先進行前向聲明

template
<class T> class Node
{
    friend 
class LList<T>;//聲明友元類,這樣LList便可以訪問Node類的私有成員
private:
    T element;
    Node 
* next;    //指向下一個結點的指針
public:
    Node(
const T& eVal, Node * nVal = NULL)
    {
        element 
= eVal;
        next 
= nVal;
    }
    Node(Node 
* nVal = NULL)
    {
        element 
= 0;
        next 
= nVal;
    }
    
~Node(){}
};

template
<class T> class LList
            :
public ListInterface<T>
{
private:
    Node
<T> * head;        //頭指針
    Node<T> * tail;        //尾指針
    Node<T> * fence;    //注意,鏈表的柵欄跟順序表AList已經完全不同了
    int leftcnt,rightcnt;    //柵欄左,右兩邊的元素個數,注意,柵欄所指的元素,屬於左邊,
                
//這與順序表不同.原因在於,鏈表有一個頭結點(head)不屬於表中元素
                
//由於已經約定了柵欄右邊的元素都必須是表中元素,而柵欄可能指
                
//向頭結點,故不應該把柵欄所指的結點歸爲右邊的元素

public:
    LList()
    {
        head 
= tail = fence = new Node<T>;
        leftcnt 
= rightcnt = 0;
    }
    
~LList()
    {
        clear();
        delete head;
    }
    
void clear()
    {
        
while (head->next != NULL)
        {
            fence 
= head;
            head 
= head->next;
            delete fence;            
        }
    }

    
bool insert(const T& element)
    {
        fence
->next = new Node<T>(element,fence->next);        //完成插入
        
        
if (fence == tail)    //如果在尾部插入了一個新元素
        {
            tail 
= fence->next;    
        }
        
++rightcnt;
        
return true;
    }

    
bool remove(T& element)
    {
        
if (fence == tail)
        {
            
return false;    //柵欄右邊沒有元素可刪除
        }

        Node
<T> * tmpPtr = fence->next;
        element 
= tmpPtr->element;    //把將刪除的值傳給外面的參數

        fence
->next = tmpPtr->next;    //完成移去

        
if (tail == tmpPtr)        //如果鏈表尾就是將要被刪除的結點
        {
            tail 
= fence;        //重新設置表尾指針
        }

        delete tmpPtr;            
//完成刪除內存
        --rightcnt;

        
return true;
    }
};

應該如何把這麼多數據結構的知識牢記在腦中,是我一直經常思考的問題。但我到現在還沒有找到一種一勞永逸的方法,事實上,我覺得一勞永逸的想法或許從根本上就是錯誤的。

要理解它!要用圖形的方法去想象它!比如鏈表的插入刪除,書中都有圖示,那就是一種把抽象的程序形象化的方法。寫程序的時候,應該是按照腦中想象的那個畫面來寫,而不是看着筆下的那張白紙。

編程,有時候就是畫畫。

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章