数据结构与算法 Chapter 2 线性表

2.1 线性表定义

线性表是由长度为n的一组节点组成的有限序列,其中除了首末结点之外,每个结点都有直接的前驱结点和后继结点。

2.2 线性表的顺序存储结构

顺序存储结构使用一组连续的存储单元来存储线性表。 其特点有:线性表的逻辑顺序与物理顺序一致、数据元素之间的关系采用物理位置的相邻来表示。其可以随机存取,常用一维数组表示:

#include <iostream>
using namespace std;

template<class Elem>
class Alist
{
private:
    int maxSize;
    int listSize;
    int curr;
    Elem * listArray;
public:
    Alist(int size = 100)
    {
        maxSize = size;
        listSize = 0;
        curr = 0;
        listArray = new Elem[maxSize];
    }
    ~Alist(){ delete [] listArray;}
    void clear() {listSize = curr = 0;}
    void Prev() {if (curr>0) curr--;}
    void Next() {if (curr < listSize-1) curr++;}
    bool setPos(int pos)
    {
        if (pos < 0 || pos >= listSize)
            return false;
        curr = pos;
    }
    bool insert(const Elem it)
    {
        if (listSize == maxSize) return false;
        for (int i = listSize; i > curr; i--)
            listArray[i] = listArray[i-1];
        listArray[curr] = it;
        listSize++; // update length of the list
        return true;
    }
    bool append(const Elem it)
    {
        if (listSize == maxSize) return false;
        listArray[listSize++] = it;
        return true;
    }
    bool remove(Elem & it)
    {
        if (!listSize) return false;
        if (curr < 0 || curr >= listSize) return false;
        it = listArray[curr];
        for (int i = curr; i < listSize-1; i++)
            listArray[i] = listArray[i+1];
        listSize--;
        curr--; // in case you removed the last one
        return true;
    }
    void print()
    {
        for (int i = 0; i < listSize; i++)
            cout << listArray[i] << " ";
        cout << endl;
    }
};

int main()
{
    int temp;
    Alist<int> mylist;
    mylist.append(2);
    mylist.append(10);
    mylist.append(8);
    mylist.append(5);
    mylist.append(7);
    mylist.print();
    mylist.setPos(3);
    mylist.insert(100);
    mylist.Next();
    mylist.print();
    mylist.remove(temp);
    mylist.remove(temp);
    mylist.remove(temp);
    mylist.print();
}
  1. 在线性表中随机访问任意位置上的元素,时间复杂度为O(1),因此顺序表示可以随机存取的。
  2. 在顺序存储的线性表中插入或删除一个节点,时间复杂度为O(n),而且需要移动大量元素。
  3. 顺序表空间扩展的代价高,往往需要重新分配。

2.3 线性表的链式存储结构

单链表

单链表的每个数据元素用一个结点来存储,每个结点包含一个数据域和一个指针域头结点是链表最开头的结点,没有存储任何数据元素,通过指针head找到,其他结点的地址通过前驱的next指针找到。最后一个结点没有后继,因此其指针域内容为NULL。下面是单链表的定义、操作实现与一个例子:

#include <iostream>
using namespace std;

template<class Elem>
class Link
{
public:
    Elem element;
    Link * next;
    Link(const Elem & item, Link * nextval = NULL) {element = item; next = nextval;}
    Link(Link * nextval = NULL) {next = nextval;}
};

template<class Elem>
class LList
{
private:
    Link<Elem> * head;
    Link<Elem> * tail;
    Link<Elem> * curr;
    void init() {curr = tail = head = new Link<Elem>;}
    void removeall()
    {
        while (head != NULL)
        {
            curr = head;
            head = head->next;
            delete curr;
        }
    }
public:
    LList() {init();}
    ~LList() {removeall();}
    bool getValue(Elem & e)
    {
        if (curr == NULL) return false;
        e = curr->element;
        return true;
    }
    Link<Elem> * Locate(Elem e)
    {
        Link<Elem> * p = head;
        while (p != NULL)
        {
            p = p->next;
            if (p->element == e)
                return p;
        }
        return NULL;
    }
    bool isEmpty() {return head->next == NULL;}
    void Prev()
    {
        if (curr == head) return;
        Link<Elem> * p = head;
        while (p->next != curr)
            p = p->next;
        curr = p;
    }
    void Next()
    {
        if (curr->next == NULL)
            return;
        curr = curr->next;
    }
    bool setPos(int pos)
    {
        Link<Elem> * p = head;
        for (int i = 0; i < pos; i++)
        {
            if (p->next == NULL)
                return false;
            p = p->next;
        }
        curr = p;
        return true;
    }
    bool Insert(Elem x)
    {
        Link<Elem> * s = new Link<Elem>;
        s->element = x;
        s->next = curr->next;
        curr->next = s;
        return true;
    }
    bool remove(Elem &e)
    {
        if (curr->next == NULL) return false;
        Link<Elem> * q = curr->next;
        e = q->element;
        curr->next = q->next;
        delete q;
        return true;
    }
    void clear() {removeall(); init();}
    void Print()
    {
        Link<Elem> * p = head;
        while (p->next != NULL)
        {
            p = p->next;
            cout << p->element << " ";
        }
        cout << endl;
    }
};

int main()
{
    int a;
    LList<int> mylist;
    mylist.Insert(2);
    mylist.Insert(3);
    mylist.Insert(8);
    mylist.Print();
    mylist.Next();
    mylist.Next();
    mylist.Insert(10);
    mylist.Prev();
    mylist.remove(a);
    mylist.Print();
    return 0;
}

在单链表上,删除和插入一个结点的时间复杂度为O(1),但随机访问任意位置上的结点时间复杂度为O(n)

顺序表与单链表比较

  1. 空间上:顺序表所有空间都用于存储数据,而单链表一部分空间用于存储指针域。
  2. 访问上:顺序表可以O(1)时间访问任意元素,而单链表随机访问的时间复杂度为O(n)。
  3. 插入删除上:顺序表的平均时间复杂度为O(n),而单链表的时间复杂度为O(1)。
    由于插入删除是更经常的操作,因此单链表在很多方面都优于顺序表。

双向链表

一个双向链表中的结点包含两个指针域和一个数据域,其中两个指针域分别为前驱后继,改进了单链表找前驱结点时间复杂度为O(n)的缺点。一个带头结点的双向链表的定义、操作实现与一个例子如下:

#include <iostream>
using namespace std;

template<class Elem>
class DLink
{
public:
    Elem element;
    DLink * prev;
    DLink * next;
    DLink(const Elem & it, DLink * p = NULL, DLink * n = NULL)
    {
        element = it;
        prev = p;
        next = n;
    }
    DLink(DLink *p = NULL, DLink *n = NULL) {prev = p; next = n;}
};

template<class Elem>
class DList
{
private:
    DLink<Elem> * head, * tail, * curr;
public:
    DList()
    {
        head = tail = curr = new DLink<Elem>;
    }
    ~DList()
    {
        while (head != NULL)
        {
            curr = head;
            head = head->next;
            delete curr;
        }
    }

    void Prev() {curr = curr->prev;}
    void Next() {curr = curr->next;}
    bool setPos(int pos)
    {
        DLink<Elem> * p = head;
        for (int i = 0; i < p; i++)
        {
            if (p->next == NULL) return false;
            p = p->next;
        }
        curr = p;
    }
    bool getValue(Elem & x)
    {
        if (head->next == NULL) return false;
        x = curr->element;
        return true;
    }
    bool Insert(const Elem x) // insert an element after curr
    {
        if (curr->next != NULL)
        {

            DLink<Elem> * newptr, * nextptr;
            nextptr = curr->next;
            newptr = new DLink<Elem>(x, curr, nextptr);
            curr->next = newptr;
            nextptr->prev = newptr;
            return true;
        }
        else // when curr->next == NULL
        {
            DLink<Elem> * newptr = new DLink<Elem>(x, curr, NULL);
            curr->next = newptr;
            return true;
        }
    }
    bool remove(Elem & x) // remove the element of curr
    {
        if (head->next == NULL || curr == NULL) return false;
        DLink<Elem> * temp = curr;
        x = temp->element;
        curr->prev->next = curr->next;
        curr->next->prev = curr->prev;
        curr = curr->next; // reset curr to a valid node
        delete temp;
        return true;
    }
    void Print()
    {
        DLink<Elem> * p = head;
        while (p->next != NULL)
        {
            p = p->next;
            cout << p->element << " ";
        }
        cout << endl;
    }
};

int main()
{
    int a;
    DList<int> mylist;
    mylist.Insert(2);
    mylist.Insert(3);
    mylist.Insert(8);
    mylist.Print();
    mylist.Next();
    mylist.Next();
    mylist.Next();
    //mylist.getValue(a);
    //cout << a << endl;
    mylist.Insert(10);
    mylist.Prev();
    mylist.remove(a);
    mylist.Print();
    return 0;
}

循环链表

循环单链表的特点在于,单链表的尾结点rear->next为空,但循环单链表的尾结点rear->next==head,这使得插入删除时不用考虑头尾节点的特殊性。循环双链表类似地有rear->next==headhead->prev==rear。下面是一个循环单链表的定义与操作实现,并用其解决Josephus问题:

#include <iostream>
using namespace std;

template<class Elem>
class CLinkNode
{
public:
    Elem element;
    CLinkNode<Elem> * next;
    CLinkNode(CLinkNode<Elem> * nexts = NULL) : next(nexts) {};
    CLinkNode(Elem d, CLinkNode<Elem> * nexts = NULL) : element(d), next(nexts) {}
};

template<class Elem>
class CList
{
private:
    CLinkNode<Elem> * head, * curr;
public:
    CList() {head = NULL; curr = NULL;}
    ~CList()
    {
        while (head != NULL)
        {
            curr = head;
            head = head->next;
            delete curr;
        }
    }
    bool Insert(const Elem x)
    {
        if (head == NULL)
        {
            head = new CLinkNode<Elem>(x, head);
            head->next = head;
            curr = head;
            curr = curr->next;
            return true;
        }
        else
        {
            CLinkNode<Elem> * newptr = new CLinkNode<Elem>(x, curr->next);
            newptr->next = curr->next;
            curr->next = newptr;
            curr = curr->next;
            return true;
        }
    }
    void Prev()
    {
        CLinkNode<Elem> * p = curr;
        while (p->next != curr)
            p = p->next;
        curr = p;
    }
    bool remove(Elem & x)
    {
        if (curr == NULL) return false;
        x = curr->element;
        CLinkNode<Elem> * temp = curr;
        Prev();
        curr->next = temp->next;
        delete temp;
        return true;
    }
    CLinkNode<Elem> * getHead() {return head;}
    void Print()
    {
        CLinkNode<Elem> * p = head;
        while (p != head)
        {
            p = p->next;
            cout << p->element << " ";

        }
        cout << endl;
    }
};

int main()
{
    int n, m;
    cin >> n >> m;
    CList<int> clist;
    for (int i = 1; i <= n; i++)
        clist.Insert(i);
    clist.Print();
    CLinkNode<int> * p = clist.getHead(), * pre = NULL;
    for (int i = 0; i < n; i++)
    {
        for (int j = 1; j < m; j++)
        {
            pre = p;
            p = p->next;
        }
        cout << "OUT: " << p->element << endl;
        m = p->element;
        pre->next = p->next;
        delete p;
        p = pre->next;
    }
    return 0;
}

2.4 线性表应用

一元多项式表示

可以利用单链表表示一元多项式,其中每个结点有两个数据与与一个指针域,两个数据域分别存储多项式当中一项的系数与次数,不同项之间的次数按从头到尾从低到高排列。在此基础上,可以定义多项式链表的加法与减法运算:

Poly sum(Poly a, Poly b)
{
    Poly c;
    Node * p = a.getHead()->next;
    Node * q = b.getHead()->next;
    Node * r = c.getHead();
    while (p && q)
    {
        if (p->exp == q->exp)
        {
            Node * s = new Node(p->coef + q->coef, p->exp, NULL);
            r->next = s;
            r = s;
            p = p->next;
            q = q->next;
        }
        else if(p->exp > q->exp)
        {
            Node * s = new Node(q->coef, q->exp, NULL);
            r->next = s;
            r = s;
            q = q->next;
        }
        else if(p->exp < q->exp)
        {
            Node * s = new Node(p->coef, p->exp, NULL);
            r->next = s;
            r = s;
            p = p->next;
        }
    }
    if (p) r->next = p;
    else r->next = q;
    return c;
}

商品链更新

与一元多项式的表示相似,也可以由单链表存储并定义加减法以追踪货物情况。

2.5 本章练习

Ex13. 设线性表L=(a1,a2,...,an)以带头结点的单链表作存储结构。请编写函数使得当n为奇数时让L成为L=(a2,a4,...,an-1,a1,...,an)

#include <iostream>
using namespace std;

template<class Elem>
class Link
{
public:
    Elem element;
    Link * next;
    Link(const Elem & item, Link * nextval = NULL) {element = item; next = nextval;}
    Link(Link * nextval = NULL) {next = nextval;}
};

template<class Elem>
class LList
{
private:
    Link<Elem> * head;
    Link<Elem> * tail;
    Link<Elem> * curr;
    void init() {curr = tail = head = new Link<Elem>;}
    void removeall()
    {
        while (head != NULL)
        {
            curr = head;
            head = head->next;
            delete curr;
        }
    }
public:
    LList() {init();}
    ~LList() {removeall();}
    bool getValue(Elem & e)
    {
        if (curr == NULL) return false;
        e = curr->element;
        return true;
    }
    Link<Elem> * Locate(Elem e)
    {
        Link<Elem> * p = head;
        while (p != NULL)
        {
            p = p->next;
            if (p->element == e)
                return p;
        }
        return NULL;
    }
    bool isEmpty() {return head->next == NULL;}
    void Prev()
    {
        if (curr == head) return;
        Link<Elem> * p = head;
        while (p->next != curr)
            p = p->next;
        curr = p;
    }
    void Next()
    {
        if (curr->next == NULL)
            return;
        curr = curr->next;
    }
    bool setPos(int pos)
    {
        Link<Elem> * p = head;
        for (int i = 0; i < pos; i++)
        {
            if (p->next == NULL)
                return false;
            p = p->next;
        }
        curr = p;
        return true;
    }
    bool Insert(Elem x)
    {
        Link<Elem> * s = new Link<Elem>;
        s->element = x;
        s->next = curr->next;
        curr->next = s;
        curr = curr->next;
        return true;
    }
    bool remove(Elem &e)
    {
        if (curr->next == NULL) return false;
        Link<Elem> * q = curr->next;
        e = q->element;
        curr->next = q->next;
        delete q;
        return true;
    }
    void clear() {removeall(); init();}
    void Print()
    {
        Link<Elem> * p = head;
        while (p->next != NULL)
        {
            p = p->next;
            cout << p->element << " ";
        }
        cout << endl;
    }
    Link<Elem> * getHead() {return head;}
};

template<class Elem>
void Shuffle(LList<Elem> & l)
{
    Link<Elem> * l2, *p, *q;
    p = l.getHead();
    l2 = new Link<Elem>; // head node
    q = l2;
    int count = 1;
    while (p->next)
    {
        if (count % 2 == 1)
        {
            q->next = p->next;
            q = q->next;
            p->next = p->next->next;
            count++;
        }
        else
        {
            p = p->next;
            count++;
        }
    }
    p->next = l2->next;
}

int main()
{
    LList<int> l;
    for (int i = 1; i < 10; i++)
        l.Insert(i);
    l.Print();
    Shuffle(l);
    l.Print();
    return 0;
}

Ex20. 写一算法将单链表中所有重复的结点删除,使所得结果表中各结点值均不相同。

#include <iostream>
using namespace std;

template<class Elem>
class Link
{
public:
    Elem element;
    Link * next;
    Link(const Elem & item, Link * nextval = NULL) {element = item; next = nextval;}
    Link(Link * nextval = NULL) {next = nextval;}
};

template<class Elem>
class LList
{
private:
    Link<Elem> * head;
    Link<Elem> * tail;
    Link<Elem> * curr;
    void init() {curr = tail = head = new Link<Elem>;}
    void removeall()
    {
        while (head != NULL)
        {
            curr = head;
            head = head->next;
            delete curr;
        }
    }
public:
    LList() {init();}
    ~LList() {removeall();}
    bool getValue(Elem & e)
    {
        if (curr == NULL) return false;
        e = curr->element;
        return true;
    }
    Link<Elem> * Locate(Elem e)
    {
        Link<Elem> * p = head;
        while (p != NULL)
        {
            p = p->next;
            if (p->element == e)
                return p;
        }
        return NULL;
    }
    bool isEmpty() {return head->next == NULL;}
    void Prev()
    {
        if (curr == head) return;
        Link<Elem> * p = head;
        while (p->next != curr)
            p = p->next;
        curr = p;
    }
    void Next()
    {
        if (curr->next == NULL)
            return;
        curr = curr->next;
    }
    bool setPos(int pos)
    {
        Link<Elem> * p = head;
        for (int i = 0; i < pos; i++)
        {
            if (p->next == NULL)
                return false;
            p = p->next;
        }
        curr = p;
        return true;
    }
    bool Insert(Elem x)
    {
        Link<Elem> * s = new Link<Elem>;
        s->element = x;
        s->next = curr->next;
        curr->next = s;
        return true;
    }
    bool remove(Elem &e)
    {
        if (curr->next == NULL) return false;
        Link<Elem> * q = curr->next;
        e = q->element;
        curr->next = q->next;
        delete q;
        return true;
    }
    void clear() {removeall(); init();}
    void Print()
    {
        Link<Elem> * p = head;
        while (p->next != NULL)
        {
            p = p->next;
            cout << p->element << " ";
        }
        cout << endl;
    }
    Link<Elem> * getHead() {return head;}
};

template<class Elem>
void noRedun(LList<Elem> & l)
{
    Link<Elem> * p, * q, * pre;
    p = l.getHead();
    p = p->next;
    q = p->next;
    pre = p;
    while (p->next)
    {
        q = p->next;
        pre = p;
        while (q)
        {
            if (p->element == q->element)
            {
                Link<Elem> * temp = q;
                pre->next = q->next;
                delete q;
                q = pre->next;
            }
            else
            {
                pre = q;
                q = q->next;
            }
        }
        p = p->next;
    }
}

int main()
{
    LList<int> l;
    for (int i = 1; i < 10; i++)
        l.Insert(rand() % 10);
    l.Print();
    noRedun(l);
    l.Print();
    return 0;
}
posted @ 2019-03-03 19:42  Gabriel_Ham  阅读(363)  评论(0编辑  收藏  举报