一点 · 线性表

线性表

线性表的定义:由成为元素的数据项组成的一种有限且有序的序列。

这里的有序是指每个元素都有自己的位置,而非元素按其值大小排序,当线性表不包含任何元素时,称为空表,元素数目称为线性表的长度,开始结点称为表头,结尾结点称为表尾,表中元素的值和它的位置可以有联系也可以无联系。

线性表的C++抽象类声明:

template <typename E> class List { // List ADT
private:
  void operator =(const List&) {}      // Protect assignment
  List(const List&) {}           // Protect copy constructor
public:
  List() {}          // Default constructor
  virtual ~List() {} // Base destructor

  // Clear contents from the list, to make it empty.
  virtual void clear() = 0;

  // Insert an element at the current location.
  // item: The element to be inserted
  virtual void insert(const E& item) = 0;

  // Append an element at the end of the list.
  // item: The element to be appended.
  virtual void append(const E& item) = 0;

  // Remove and return the current element.
  // Return: the element that was removed.
  virtual E remove() = 0;

  // Set the current position to the start of the list
  virtual void moveToStart() = 0;

  // Set the current position to the end of the list
  virtual void moveToEnd() = 0;

  // Move the current position one step left. No change
  // if already at beginning.
  virtual void prev() = 0;

  // Move the current position one step right. No change
  // if already at end.
  virtual void next() = 0;

  // Return: The number of elements in the list.
  virtual int length() const = 0;

  // Return: The position of the current element.
  virtual int currPos() const = 0;

  // Set current position.
  // pos: The position to make current.
  virtual void moveToPos(int pos) = 0;

  // Return: The current element.
  virtual const E& getValue() const = 0;
};


顺序表实现方式:

template <typename E> // Array-based list implementation
class AList : public List<E> {
private:
  int maxSize;        // Maximum size of list
  int listSize;       // Number of list items now
  int curr;           // Position of current element
  E* listArray;    // Array holding list elements

public:
  AList(int size=defaultSize) { // Constructor
    maxSize = size;
    listSize = curr = 0;
    listArray = new E[maxSize];
  }

  ~AList() { delete [] listArray; } // Destructor

  void clear() {                    // Reinitialize the list
    delete [] listArray;            // Remove the array
    listSize = curr = 0;            // Reset the size
    listArray = new E[maxSize];  // Recreate array
  }

  // Insert "it" at current position
  void insert(const E& it) {
    Assert(listSize < maxSize, "List capacity exceeded");
    for(int i=listSize; i>curr; i--)  // Shift elements up
      listArray[i] = listArray[i-1];  //   to make room
    listArray[curr] = it;
    listSize++;                       // Increment list size
  }

  void append(const E& it) {       // Append "it"
    Assert(listSize < maxSize, "List capacity exceeded");
    listArray[listSize++] = it;
  }

  // Remove and return the current element.
  E remove() {
    Assert((curr>=0) && (curr < listSize), "No element");
    E it = listArray[curr];           // Copy the element
    for(int i=curr; i<listSize-1; i++)  // Shift them down
      listArray[i] = listArray[i+1];
    listSize--;                          // Decrement size
    return it;
  }
  void moveToStart() { curr = 0; }        // Reset position
  void moveToEnd() { curr = listSize; }     // Set at end
  void prev() { if (curr != 0) curr--; }       // Back up
  void next() { if (curr < listSize) curr++; } // Next

  // Return list size
  int length() const  { return listSize; }

  // Return current position
  int currPos() const { return curr; }

  // Set current list position to "pos"
  void moveToPos(int pos) {
    Assert ((pos>=0)&&(pos<=listSize), "Pos out of range");
    curr = pos;
  }

  const E& getValue() const { // Return current element
    Assert((curr>=0)&&(curr<listSize),"No current element");
    return listArray[curr];
  }
};


在顺序表中moveToPos()、getValue()和append()均只需要Θ(1)时间,而insert(), remove()则需要Θ(N)时间。


链表实现方式:

首先需要包含一个储存元素值的element域和一个储存表中下一个节点指针的next域的Link类

template <typename E> class Link {
public:
  E element;      // Value for this node
  Link *next;        // Pointer to next node in list
  // Constructors
  Link(const E& elemval, Link* nextval =NULL)
    { element = elemval;  next = nextval; }
  Link(Link* nextval =NULL) { next = nextval; }
};

由于采取使curr指向前一个元素,以便轻松地在curr之后插入新元素,所以需要一个特殊的空表头结点,使head指向它,这样就可以进行正常的添加删除操作,而在初始化的时候,就将head,tail,curr均指向该空结点。
template <typename E> class LList: public List<E> {
private:
  Link<E>* head;       // Pointer to list header
  Link<E>* tail;       // Pointer to last element
  Link<E>* curr;       // Access to current element
  int cnt;    	       // Size of list

  void init() {        // Intialization helper method
    curr = tail = head = new Link<E>;
    cnt = 0;
  }

  void removeall() {   // Return link nodes to free store
    while(head != NULL) {
      curr = head;
      head = head->next;
      delete curr;
    }
  }

public:
  LList(int size=defaultSize) { init(); }    // Constructor
  ~LList() { removeall(); }                   // Destructor
  void print() const;                // Print list contents
  void clear() { removeall(); init(); }       // Clear list

  // Insert "it" at current position
  void insert(const E& it) {
    curr->next = new Link<E>(it, curr->next);  
    if (tail == curr) tail = curr->next;  // New tail
    cnt++;
  }

  void append(const E& it) { // Append "it" to list
    tail = tail->next = new Link<E>(it, NULL);
    cnt++;
  }

  // Remove and return current element
  E remove() {
    Assert(curr->next != NULL, "No element");
    E it = curr->next->element;      // Remember value
    Link<E>* ltemp = curr->next;     // Remember link node
    if (tail == curr->next) tail = curr; // Reset tail
    curr->next = curr->next->next;   // Remove from list
    delete ltemp;                    // Reclaim space
    cnt--;			     // Decrement the count
    return it;
  }

  void moveToStart() // Place curr at list start
    { curr = head; }

  void moveToEnd()   // Place curr at list end
    { curr = tail; }

  // Move curr one step left; no change if already at front
  void prev() {
    if (curr == head) return;       // No previous element
    Link<E>* temp = head;
    // March down list until we find the previous element
    while (temp->next!=curr) temp=temp->next;
    curr = temp;
  }

  // Move curr one step right; no change if already at end
  void next()
    { if (curr != tail) curr = curr->next; }

  int length() const  { return cnt; } // Return length

  // Return the position of the current element
  int currPos() const {
    Link<E>* temp = head;
    int i;
    for (i=0; curr != temp; i++)
      temp = temp->next;
    return i;
  }

  // Move down list to "pos" position
  void moveToPos(int pos) {
    Assert ((pos>=0)&&(pos<=cnt), "Position out of range");
    curr = head;
    for(int i=0; i<pos; i++) curr = curr->next;
  }

  const E& getValue() const { // Return current element
    Assert(curr->next != NULL, "No value");
    return curr->next->element;
  }
};

remove函数中为了防止“丢失”被删除节点的内存,因此先将要删除的指针指向临时指针Itemp,然后调用delete函数释放被删除结点占用的内存。

链表中除了prev一般需要Θ(n)时间,而moveToPos(i)需要Θ(i)时间以外,其余操作只需Θ(1)时间。

而为了简化创建和删除结点的操作,在Link类中添加可以自己管理的可利用空间表,因此可以采用C++操作符重载方法来替代new和delete
template <typename E> class Link {
private:
 static Link<E>* freelist;
public:
  E element;      // Value for this node
  Link *next;        // Pointer to next node in list
  // Constructors
  Link(const E& elemval, Link* nextval =NULL)
    { element = elemval;  next = nextval; }
  Link(Link* nextval =NULL) { next = nextval; }

  void* operator new(size_t) {
	  if (freelist == NULL) return ::new Link;
	  Link<E>* temp = freelist;
	  freelist = freelist->next;
	  return temp;
  }

  void operator delete(void* ptr) {
	  ((Link<E>*)ptr)->next = freelist;
	  freelist = (Link<E>*)ptr;
  }
};

template <typename E>
Link<E>* Link<E>::freelist = NULL;

顺序表的缺点显而易见,需要预先设定长度,如果未用完,造成浪费,如果过多,则装不下,相比之下,链表更加灵活;顺序表的优点在于,每一个元素没有造成空间浪费,而链表则优于存储指针信息,而造成了结构性开销(并非存储真正数据的信息所占空间)。综上可知,在未知元素多少的情况下,链表更好,在确定元素多少时,顺序表更节省空间。

而具体的选择,可以根据以下这个简单的公式来判断:
n>DE/(P+E)
n:线性表中当前元素的数目  P:指针的储存单元大小  E:数据元素的储存单元大小  D:数组中储存的线性表元素的最大数目
那么可知顺序表的空间需求为:DE,而链表的空间需求则与n有关:n(P+E),由此可以得到上述公式,并得到n的临界值DE(P+E),当n大于此值时,顺序表更优,反之则反。

在链表的基础上,有双链表可以进行从前向后和从后向前的双向操作,而仅需要添加一个prev指针用于指向前一结点和对部分函数的操作做些许调整即可。
此处,考虑到在链表实现时,添加空表头结点以更好的实现从前向后的功能,这里双向链表相应的就需要在尾部再增加一个空表尾结点(tail指向它),以更好的实现从后向前的功能。因此,在初始化的时候head和tail分别指向头尾空结点,而next与prev则分别指向尾头空结点。

PS:在单项链表实现中,仍可采用更符合直观逻辑的curr指向当前结点,而非当前结点的前驱结点,但此时造成的问题是前驱结点的next无法更新,可采用以下替代方案:在当前结点之后添加一个结点, 将当前元素值复制到其中,同时将新值插入到那个老的当前结点之中。
对此方案的理解,可以称之为,老人传魂,在此处可以通过日本一恐怖短片《奶奶》来更好的理解。
我们首先假称结点值为 魂,而结点为 身体,结点值放入结点中则为完整的人,魂与身体可以分离并交换。
在这里就直接讲述《奶奶》这个故事来理解这个方案:
奶奶病危,这时,奶奶请求与孙女交换身体,去实现自己的遗愿,孙女心软同意,让奶奶占据了自己的身体,而自己的魂则代替奶奶存在于病危的身体中,受苦。
可以将奶奶病危,看作要添入新结点,这时,通过将老结点的值即奶奶的魂,传到新结点即新身体之中,便可以让老结点值一直存在,而从外界来看,依然是加入了新值即“奶奶”“正常”死去,“孙女”健康长大。
由于这个原因,此处也不需要再有空表头结点了,相反,需要空表尾结点,不然,便永远也无法删除掉最后一个结点,因为它会不断地将自己放到最后。所以需要有一个预备的结点,才可以停止让最后一个结点“金蝉脱壳”。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值