六、线性表——链式描述

56 阅读5分钟

知识

1. 单向链表

描述

元素是节点,节点包含数据元素链域(指向下一个元素的指针)

结构chainNode

template<class T>
struct chainNode {

    //data
    T element;
    chainNode<T> *next;

    //functions
    chainNode() {};

    chainNode(const T &element) { this->element = element; }

    chainNode(const T &element, chainNode *next) {
        this->element = element;
        this->next = next;
    }
};

类chain

template<class T>
class chain {
protected:
    chainNode<T> *firstNode;
    int listSize;

    void checkIndex(int theIndex) const;

public:
    explicit chain(int capacity = 10);

    chain(const chain &);

    ~chain();

    bool empty() const { return listSize == 0; }

    int size() const { return listSize; }

    T &get(int theIndex) const;

    int indexOf(const T &theElement) const;

    void erase(int theIndex);

    void insert(int index, const T &theElement);

    void output(ostream &) const;

    void setSize(int theSize);

};

template<class T>
void chain<T>::checkIndex(int theIndex) const {
    if (theIndex > listSize - 1 || theIndex < 0)
        cout << "listSize: " << listSize << " index: " << theIndex << endl;
}

template<class T>
chain<T>::chain(const chain<T> &theList) {
    listSize = theList.listSize;

    if (listSize == 0) {//链表为空
        firstNode = NULL;
        return;
    }

    chainNode<T> *sourceNode = theList.firstNode;
    firstNode = new chainNode<T>(sourceNode->element);
    sourceNode = sourceNode->next;
    chainNode<T> *targetNode = firstNode;

    while (sourceNode != NULL) {
        targetNode->next = new chainNode<T>(sourceNode->element);
        sourceNode = sourceNode->next;
        targetNode = targetNode->next;
    }
    targetNode->next = NULL;
}

template<class T>
chain<T>::chain(int capacity) {
    if (capacity < 1)
        cout << "Must > 0" << endl;
    firstNode = NULL;
    listSize = 0;
}

template<class T>
chain<T>::~chain() {
    while (firstNode != NULL) {
        chainNode<T> *nextNode = firstNode->next;
        delete firstNode;
        firstNode = nextNode;
    }
}

template<class T>
T &chain<T>::get(int theIndex) const {
    checkIndex(theIndex);

    chainNode<T> *currentNode = firstNode;
    for (int i = 0; i < theIndex; i++)
        currentNode = currentNode->next;
    return currentNode->element;
}

template<class T>
int chain<T>::indexOf(const T &theElement) const {
    //返回元素theElement首次出现的索引
    //若该元素不存在,返回-1

    //搜索链表寻找元素theElement
    chainNode<T> *currentNode = firstNode;
    int index = 0;//当前索引
    while (currentNode != NULL && currentNode->element != theElement) {
        //移向下一个节点
        currentNode = currentNode->next;
        index++;
    }
    //判断是否找到元素
    if (currentNode == NULL)
        return -1;
    else
        return index;
}

template<class T>
void chain<T>::erase(int theIndex) {
    //删除索引为theIndex的元素
    //若元素不存在,抛出异常
    checkIndex(theIndex);

    //索引有效,找出要删除的元素节点
    chainNode<T> *deleteNode;
    if (theIndex == 0) {
        //删除链表的首节点
        deleteNode = firstNode;
        firstNode = firstNode->next;
    } else {
        //用指针p指向要删除元素的前驱节点
        chainNode<T> *p = firstNode;
        for (int i = 0; i < theIndex - 1; i++)
            p = p->next;

        deleteNode = p->next;
        p->next = p->next->next;//删除deleteNode指向的节点
    }

    delete deleteNode;
    listSize--;
}

template<class T>
void chain<T>::insert(int theIndex, const T &theElement) {
    //在索引为theIndex位置插入theElement

    if (theIndex < 0 || theIndex > listSize) {
        //无效索引
        cout << "theIndex is " << theIndex << ", listSize is " << listSize << endl;
        return;
    }

    if (theIndex == 0) {
        //在链表头插入
        firstNode = new chainNode<T>(theElement, firstNode);
    } else {
        //寻找新元素的前驱
        chainNode<T> *p = firstNode;
        for (int i = 0; i < theIndex - 1; i++)
            p = p->next;
        //在p后插入
        p->next = new chainNode<T>(theElement, p->next);
    }
    listSize++;
}

template<class T>
void chain<T>::output(ostream &out) const {
    for (chainNode<T> *currentNode = firstNode; currentNode != NULL; currentNode = currentNode->next)
        out << currentNode->element << ' ';
}

template<class T>
ostream &operator<<(ostream &out, const chain<T> &x) {
    x.output(out);
    return out;
}

template<class T>
void chain<T>::setSize(int theSize) {
    if (listSize > theSize) {
        listSize = theSize;
        chainNode<T> *currentNode = firstNode;
        for (int i = 0; i < theSize; i++)
            currentNode = currentNode->next;
        currentNode->next = nullptr;
    }
}

分析评价

  • 优点
  1. 动态大小。单向链表大小不固定,可以动态变化。
  2. 插入删除高效。只需要改变指针指向。
  3. 内存使用。不需要连续的内存,减少零碎内存。
  • 不足
  1. 顺序访问。不支持随机访问
  2. 反向遍历。不能进行返向遍历
  3. 额外内存。需要额外内存储存指针
  • 应用场景
    数据经常变化,不需快速访问;增删频繁,内存有限。

2. 双向循环链表

描述

在单向链表基础上,加入头结点与指向前驱的指针。

类node

template<class T>
class node {
public:
    node<T> *previous;
    node<T> *next;
    T element;

    node() {}

    node(T element) : element(element), previous(nullptr), next(nullptr) {}

    node(T element, node<T> *previous, node<T> *next) : element(element), previous(previous), next(next) {}
};

类doublyCircularList

template<class T>
class doublyCircularList {
    node<T> *headerNode;
    int listSize;

    void checkIndex(int theIndex) {
        if (theIndex < 0 || theIndex > listSize) {
            cout << "listSize: " << listSize << ", theIndex :" << theIndex << endl;
            return;
        }
    }

public:
    doublyCircularList() {
        headerNode = new node<T>();
        headerNode->next = headerNode;
        headerNode->previous = headerNode;
        listSize = 0;
    }

    doublyCircularList(const doublyCircularList<T> &x) {
        listSize = x.listSize;

        if (listSize == 0) {
            headerNode->next = headerNode;
            headerNode->previous = headerNode;
        } else {
            node<T> *sourceNode = x.headerNode->next;
            headerNode->next = new chainNode<T>(sourceNode->element);
            headerNode->next->previous = headerNode;

            sourceNode = sourceNode->next;
            node<T> *targetNode = headerNode->next;
            while (sourceNode != headerNode) {
                targetNode->next = new node<T>(sourceNode->element);
                targetNode->next->previous = targetNode;

                targetNode = targetNode->next;
                sourceNode = sourceNode->next;
            }
            sourceNode->next = headerNode;
            sourceNode->next->previous = sourceNode;
        }
    }

    ~doublyCircularList() {
        while (headerNode->next != headerNode) {
            node<T> *nextNode = headerNode->next->next;
            delete headerNode->next;
            headerNode->next = nextNode;
        }
    }

    bool empty() { return listSize == 0; }

    int size() { return listSize; }

    T &get(int theIndex) {
        checkIndex(theIndex);

        if (theIndex < listSize / 2) {
            node<T> *p = headerNode->next;
            for (int i = 0; i < theIndex; i++) {
                p = p->next;
            }
            return p->element;
        } else {
            node<T> *p = headerNode->previous;
            for (int i = 0; i < listSize - 1 - theIndex; i++) {
                p = p->previous;
            }
            return p->element;
        }

    }

    int indexOf(const T &x) const {
        int index = 0;
        node<T> *p = headerNode->next;
        for (; p->element != x && p != headerNode;) {
            p = p->next;
            index++;
        }

        if (p == headerNode)
            return -1;
        else return index;
    }

    void erase(int theIndex) {
        checkIndex(theIndex);

        node<T> *deleteNode;
        if (theIndex < listSize / 2) {
            if (theIndex == 0) {
                deleteNode = headerNode->next;
                headerNode->next = deleteNode->next;
                deleteNode->next->previous = headerNode;
            } else {
                node<T> *p = headerNode->next;
                for (int i = 0; i < theIndex - 1; i++)
                    p = p->next;
                deleteNode = p->next;
                p->next = deleteNode->next;
                deleteNode->next->previous = p;
            }
        } else {
            node<T> *p = headerNode->previous;
            for (int i = 0; i < listSize - theIndex - 2; i++)
                p = p->previous;
            deleteNode = p->previous;
            p->previous = deleteNode->previous;
            deleteNode->previous->next = p;
        }
        delete deleteNode;
        deleteNode = nullptr;
        listSize--;
    }

    void insert(int theIndex, const T &x) {
        checkIndex(theIndex);

        if (theIndex == 0) {
            headerNode->next = new node<T>(x, headerNode, headerNode);
            headerNode->previous = headerNode->next;
        } else {
            node<T> *p = headerNode->next;
            for (int i = 0; i < theIndex - 1; i++)
                p = p->next;
            p->next = new node<T>(x, p, p->next);
            p->next->next->previous = p->next;
        }
        listSize++;
    }

    void output(ostream &out) const {
        chainNode<T> *p = headerNode->next;
        while (p != headerNode) {
            cout << p->element << ' ';
            p = p->next;
        }
    }
};

template<class T>
ostream &operator<<(ostream &out, const doublyCircularList<T> &x) {
    x.output(out);
    return out;
}

性能评价

  • 优点
  1. 支持双向遍历
  2. 动态大小
  3. 增删方便

应用

1. 箱子排序

分为两步:

  • 元素归并到对应的箱子
  • 箱子整合
template<class T>
void chain<T>::binSort(int range) {
    //sort

    //initiating
    chainNode<T> **top = new chainNode<T> *[range + 1];
    chainNode<T> **bottom = new chainNode<T> *[range + 1];
    for (int i = 0; i <= range; i++)
        bottom[i] = nullptr;

    //distributing
    for (; firstNode != nullptr; firstNode = firstNode->next) {
        int bin = firstNode->element;
        if (bottom[bin] == nullptr)
            top[bin] = bottom[bin] = firstNode;
        else {
            top[bin]->next = firstNode;
            top[bin] = firstNode;
        }
    }

    //collecting
    chainNode<T> *y = nullptr;
    for (int bin = 0; bin <= range; bin++) {
        if (bottom[bin] != nullptr) {
            if (y == nullptr)
                firstNode = bottom[bin];
            else
                y->next = bottom[bin];
            y = top[bin];
        }
    }

    if (y != nullptr)
        y->next = nullptr;
    delete[] top;
    delete[] bottom;
}

2. 基数排序

通过将数字转换为指定基数表达的数据逐步箱子排序,由于是稳定排序,结果是排序的。

3. 凸包

4. 并查集

#include<bits/stdc++.h>
using namespace std;
const int N = 100001;
int pre[N];
int rankk[N];

void init(int k){
    for(int i = 0; i <= k; i++){
        pre[i] = i;
        rankk[i] = 1;
    }
}

int find(int x){
    if(pre[x] == x)
        return x;
    else pre[x] = find(pre[x]);
}

void unite(int x, int y){
    x = find(x);
    y = find(y);
    if(x != y){
        if(rankk[x] < rankk[y])
            pre[x] = y;
        else{
            if(rankk[x] == rankk[y])
                rankk[x]++;
            pre[y] = x;
        }
    }
}

练习www.luogu.com.cn/problem/P11…