线性表及其应用

247 阅读13分钟

顺序表

使用数组实现

构造函数

template<class T>
arrayList<T>::arrayList(int initialcapacity) {
    if (initialcapacity < 1) {
      throw "初始数组容量必须大于 0";
    }
    arrayLength = initialcapacity;
    element = new T[arrayLength];
    listSize = 0;
}

时间复杂度:

O(1)O(1)

复制构造函数

template<class T>
arrayList<T>::arrayList(const arrayList<T>& theList) {
    arrayLength = theList.arrayLength; // 数组容量
    listSize = theList.listSize; // 元素个数
    element = new T[arrayLength];
    copy(theList.element, theList.element + listSize, element);
}

时间复杂度:

O(listSize)O(listSize)

改变数组长度

template<class T>
void changeLength(T*& a, int oldLength, int newLength) {
    if (newLength < 0) {
        throw "新长度必须大于 0";
    }
    T* temp = new T[newLength]; // 新数组
    
    // 需要复制的元素个数
    int number = min(oldLength, newLength);
    
    // 拷贝元素到新数组(temp)
    copy(a, a + number, temp);
    
    delete[] a; // 释放老数组的内存空间
    a = temp; // 重新指向新数组
};

创建新数组:

O(1)O(1)

复制到新数组:

O(number)O(number)

插入元素

template<class T>
void arrayList<T>::insert(int theIndex, const T& theElement) {
    // 在索引 theIndex 处插入元素 theElement
    if (theIndex < 0 || theIndex > listSize) {
        // 无效索引
        ostringstream s;
        s << "index = " << theIndex << " size = " << listSize;
        throw s.str();
    }

    // 有效索引, 确定数组是否已满
    if (listSize == arrayLength) {
        // 数组空间已满, 数组长度倍增
        changeLength(element, arrayLength, 2 * arrayLength);
        arrayLength *= 2;
    }
    // 把元素向右移动一个位置
    copy_backward(element + theIndex, element + listSize, element + listSize + 1);

    element[theIndex] = theElement;
    listSize++; // 元素个数加 1
}

如果是无效索引, 时间复杂度:

O(1)O(1)

如果数组要加倍, 时间复杂度:

O(listSize)O(listSize)

移动元素, 时间复杂度:

O(listSizetheIndex)O(listSize-theIndex)

为什么需要以倍数的方式增加?
长度为 1 的空表, 如果连续执行 n=2k1n=2^k-1 次插入, 每次扩容 1 位, 假设均插入尾部, 则插入的时间复杂度为 O(n)O(n), 扩容(实际上为创建新数组, 包括初始长度为 1 的空表)的时间复杂度为 1+2++n=O(n2)1+2+\cdots+n=O(n^2), 这里每次扩容 2 位也一样
如果 2 倍增加, 则插入时间复杂度依旧是O(n)O(n), 但是扩容的时间复杂度为 1+2+4++2k=12k+112=O(2k+11)=O(n)1+2+4+\cdots+2^k=\frac{1-2^{k+1}}{1-2}=O(2^{k+1}-1)=O(n)
如果成 i 倍增长(如果 i 不是整数, 则每次扩容要取整, 比如 i=1.5, 第一次扩容为 1.5=2⌈1.5⌉=2, 第二次扩容为 3, 第三次为 4.5=5⌈4.5⌉=5), 令 n=ik1n=i^k-1, 则其时间复杂度为 1+i+i2++ik=1ik+11i=O(1i(n+1)1i)=O(n)1+i+i^2+\cdots+i^k=\frac{1-i^{k+1}}{1-i}=O(\frac{1-i(n+1)}{1-i})=O(n)

顺序存储的线性表, 扩容系数为 1.5 倍时, 尾部插入一个元素的平均时间复杂度为?
前面已知扩容的时间复杂度为 O(n)O(n), 平均到 n 个元素上, 就是 O(1)O(1), 尾部插入的时间复杂度为 O(1)O(1), 整体时间复杂度就是 O(1)O(1)

顺序存储的线性表, 扩容系数为常数 2 时, 尾部插入一个元素的平均时间复杂度为?
注意这里不是扩容 2 倍, 而是扩容系数, 也就是容量 +2, 那么扩容时间复杂度为 O(n2)O(n^2), 平均一下就是 O(n)O(n), 尾部插入是 O(1)O(1), 整体就是 O(n)O(n)

判断索引有效性

template<class T>
void arrayList<T>::checkIndex(int theIndex) const {
    // 确定索引 theIndex 在 0 和 listSize - 1 之间
    if (theIndex < 0 || theIndex >= listSize) {
        ostringstream s;
        s << "index = " << theIndex << " size = " << listSize;
        throw s.str();
    }
}

时间复杂度:

O(1)O(1)

返回索引对应的数据

template<class T>
T& arrayList<T>::get(int theIndex) const {
    checkIndex(theIndex); // 若此元素不存在, 则抛出异常
    return element[theIndex];
}

时间复杂度:

O(1)O(1)

删除元素

template<class T>
void arrayList<T>::erase(int theIndex) {
    // 删除其索引为 theIndex 的元素
    // 如果该元素不存在, 则抛出异常 illegalIndex
    checkIndex(theIndex);
    // 把 theIndex 后面的往前移一位
    // 即把 theIndex 后面的数据复制到 theIndex 的位置
    copy(element + theIndex + 1, element + listSize, element + theIndex);
    element[--listSize].~T(); // 调用数组的析构函数, 删掉最后一个元素
}

如果没有要删除的元素, 时间复杂度:

O(1)O(1)

如果存在要删除的元素, 时间复杂度:

O(listSizetheIndex)O(listSize-theIndex)

第一次出现的下标

template<class T>
int arrayList<T>::indexOf(const T& theElement) const {
    int theIndex = (int)(find(element, element + listSize, theElement) - element);
    // 确定元素 theElement 是否找到
    if (theIndex == listSize) // 没有找到
        return -1;
    else return theIndex;

    return 0;
}

时间复杂度:

O(max{listSize,1})O(max\{listSize, 1\})
完整代码
#include<iostream>
#include<sstream>
using namespace std;

// 抽象类
template<class T> class linearList {
public:
    virtual ~linearList() {};
    // 判断线性表是否为空
    virtual bool empty() const = 0;
    // 返回线性表的元素个数
    virtual int size() const = 0;
    // 返回索引为 theIndex 的元素
    virtual T& get(int theIndex) const = 0;
    // 返回元素theElement第一次出现时的索引
    virtual int indexOf(const T& theElement) const = 0;
    // 删除索引为 theIndex 的元素
    virtual void erase(int theIndex) = 0;
    // 把 theElement 插入线性表中索引为 theIndex 的位置上
    virtual void insert(int theIndex, const T& theElement) = 0;
    // 把线性表插入输出流 out
    virtual void output(ostream& out) const = 0;
};

// 改变数组长度
template<class T>
void changeLength(T*& a, int oldLength, int newLength) {
    if (newLength < 0) {
        throw "新长度必须大于 0";
    }
    T* temp = new T[newLength]; // 新数组
    int number = min(oldLength, newLength); // 需要复制的元素个数
    copy(a, a + number, temp); // 拷贝元素到新数组(temp)
    delete[] a; // 释放老数组的内存空间
    a = temp; // 重新指向新数组
};

template<class T>
class arrayList:public linearList<T> {
public:
    // 构造函数
    arrayList(int initialcapacity = 10);
    // 复制构造函数
    arrayList(const arrayList<T>&);
    // 析构函数
    ~arrayList() {
        delete[] element;
    }

    // 类方法
    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 theIndex, const T& theElement);
    void output(ostream& out) const;

    int capacity() const {
        return arrayLength;
    }
protected:
    // 若索引 theIndex 无效, 则抛出异常
    void checkIndex(int theIndex) const;

    T* element; // 一维数组
    int arrayLength; // 数组容量
    int listSize; // 元素个数
};

// 构造函数
template<class T>
arrayList<T>::arrayList(int initialcapacity) {
    if (initialcapacity < 1) {
      throw "初始数组容量必须大于 0";
    }
    arrayLength = initialcapacity;
    element = new T[arrayLength];
    listSize = 0;
}

// 复制构造函数
template<class T>
arrayList<T>::arrayList(const arrayList<T>& theList) {
    arrayLength = theList.arrayLength; // 数组容量
    listSize = theList.listSize; // 元素个数
    element = new T[arrayLength];
    copy(theList.element, theList.element + listSize, element);
}

// 判断索引有效性
template<class T>
void arrayList<T>::checkIndex(int theIndex) const {
    // 确定索引 theIndex 在 0 和 listSize - 1 之间
    if (theIndex < 0 || theIndex >= listSize) {
        throw "索引非法";
    }
}

// 返回索引为 theIndex 的元素
template<class T>
T& arrayList<T>::get(int theIndex) const {
    checkIndex(theIndex); // 若此元素不存在, 则抛出异常
    return element[theIndex];
}

// 返回元素 theElement 第一次出现时的索引
// 若该元素不存在, 则返回 - 1
template<class T>
int arrayList<T>::indexOf(const T& theElement) const {
    int theIndex = (int)(find(element, element + listSize, theElement) - element);
    // 确定元素 theElement 是否找到
    if (theIndex == listSize) // 没有找到
        return -1;
    else return theIndex;

    return 0;
}

template<class T>
void arrayList<T>::erase(int theIndex) {
    // 删除其索引为 theIndex 的元素
    // 如果该元素不存在, 则抛出异常 illegalIndex
    checkIndex(theIndex);
    // 把 theIndex 后面的往前移一位
    // 即把 theIndex 后面的数据复制到 theIndex 的位置
    copy(element + theIndex + 1, element + listSize, element + theIndex);
    element[--listSize].~T(); // 调用数组的析构函数, 删掉最后一个元素
}

template<class T>
void arrayList<T>::insert(int theIndex, const T& theElement) {
    // 在索引 theIndex 处插入元素 theElement
    if (theIndex < 0 || theIndex > listSize) {
        // 无效索引
        throw "索引非法";
    }

    // 有效索引, 确定数组是否已满
    if (listSize == arrayLength) {
        // 数组空间已满, 数组长度倍增
        changeLength(element, arrayLength, 2 * arrayLength);
        arrayLength *= 2;
    }
    // 把元素向右移动一个位置
    copy_backward(element + theIndex, element + listSize, element + listSize + 1);

    element[theIndex] = theElement;
    listSize++; // 元素个数加 1
}

template<class T>
void arrayList<T>::output(ostream& out) const {
    // 把线性表插入输出流
    copy(element, element + listSize, ostream_iterator<T>(cout, " "));
}

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

int main() {
    arrayList<int> list(10);
    list.insert(0, 10);
    list.insert(1, 18);
    cout << "当前栈: " << list << endl;
    list.erase(1);
    cout << "删除索引 1 的元素后: " << list << endl;
    return 0;
}

单向链表

构造函数

template<class T>
chain<T>::chain(int initialcapacity) {
    if (initialcapacity < 1) {
        throw "初始容量必须大于 0";
    }
    firstNode = NULL;
    listSize = 0;
}

时间复杂度:

O(1)O(1)

复制构造函数

// 复制构造函数
template<class T>
chain<T>::chain(const chain<T>& theList) {
    listSize = theList.listSize;
    // 如果链表 theList 为空
    if (listSize == 0) {
        firstNode = NULL;
        return;
    }

    // 如果链表 theList 非空
    // 要复制链表 theList 的节点
    chainNode<T>* sourceNode = theList.firstNode;

    // 复制链表 theList 的首元素
    firstNode = new chainNode<T>(sourceNode->element);

    // sourceNode 指向下一个节点
    sourceNode = sourceNode->next;
    // targetNode 指向当前节点
    chainNode<T>* targetNode = firstNode;

    // 遍历节点, 直到最后指向 NULL
    while (sourceNode != NULL) {
        // 复制剩余元素
        targetNode->next = new chainNode<T>(sourceNode->element);
        targetNode = targetNode->next;
        sourceNode = sourceNode->next;
    }
    targetNode->next = NULL; // 链表结束
}

时间复杂度:

O(max{listSize,thiList.listSize})O(max\{listSize, thiList.listSize\})

析构函数

template<class T>
chain<T>::~chain() {
    while (firstNode != NULL) {
        // 不断删除首节点, 则下一个节点就成为首节点
        chainNode<T>* nextNode = firstNode->next;
        delete firstNode;
        firstNode = nextNode;
    }
}

时间复杂度:

O(listSize)O(listSize)

插入元素

template<class T>
void chain<T>::insert(int theIndex, const T& theElement) {
    // 在索引为 theIndex 的位置上插入元素 theElement
    if (theIndex < 0 || theIndex > listSize) {
        // 无效索引, 即小于 0 和多于当前元素个数
        throw "无效索引";
    }
    else 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;
        }
        // 前驱节点的下一个节点是 theElement
        // theElement 的下一个节点是前驱结点的下一个节点
        p->next = new chainNode<T>(theElement, p->next);
    }
    listSize++;
}

时间复杂度:

O(theIndex)O(theIndex)

删除元素

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;
        // 元素个数减一
        listSize--;
        // 删除 deleteNode 指向的节点
        delete deleteNode;
    }
}

有效索引时, 时间复杂度:

O(theIndex)O(theIndex)

第一次出现索引

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

    // 搜索链表寻找元素 theElement
    chainNode<T>* currentNode = firstNode;
    int index = 0;

    // 直到当前节点等于 theElement 或者 NULL 时退出
    while (currentNode != NULL && currentNode->element != theElement) {
        // 移向下一个节点
        currentNode = currentNode->next;
        index++;
    }
    // 确定是否找到所需的元素
    if (currentNode == NULL)
        return -1;
    else
        return index;
}

平均时间复杂度:

O(listSize2)=O(listSize)O\left(\frac{listSize}{2}\right) = O(listSize)

获取索引对应的值

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;
}

时间复杂度:

O(theIndex)O(theIndex)
完整代码
#include<iostream>
using namespace std;

// 抽象类
template<class T> class linearList {
public:
    virtual ~linearList() {};
    // 判断线性表是否为空
    virtual bool empty() const = 0;
    // 返回线性表的元素个数
    virtual int size() const = 0;
    // 返回索引为 theIndex 的元素
    virtual T& get(int theIndex) const = 0;
    // 返回元素theElement第一次出现时的索引
    virtual int indexOf(const T& theElement) const = 0;
    // 删除索引为 theIndex 的元素
    virtual void erase(int theIndex) = 0;
    // 把 theElement 插入线性表中索引为 theIndex 的位置上
    virtual void insert(int theIndex, const T& theElement) = 0;
    // 把线性表插入输出流 out
    virtual void output(ostream& out) const = 0;
};

// 节点元素类, 具有 next 指针和 element 数据
template <class T>
struct chainNode {
    // 数据成员
    T element;
    chainNode<T>* next;

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

template<class T>
class chain:public linearList<T> {
public:
    // 构造函数
    chain(int initialcapacity = 10);
    // 复制构造函数
    chain(const chain<T>&);
    // 析构函数
    ~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 theIndex, const T& theElement);
    void output(ostream& out)const;

protected:
    void checkIndex(int theIndex)const;
    chainNode<T>* firstNode; // 指向链表第一个节点的指针
    int listSize; // 元素个数
};


// 构造函数
template<class T>
chain<T>::chain(int initialcapacity) {
    if (initialcapacity < 1) {
        throw "初始容量必须大于 0";
    }
    firstNode = NULL;
    listSize = 0;
}

// 复制构造函数
template<class T>
chain<T>::chain(const chain<T>& theList) {
    listSize = theList.listSize;
    // 如果链表 theList 为空
    if (listSize == 0) {
        firstNode = NULL;
        return;
    }

    // 如果链表 theList 非空
    // 要复制链表 theList 的节点
    chainNode<T>* sourceNode = theList.firstNode;

    // 复制链表 theList 的首元素
    firstNode = new chainNode<T>(sourceNode->element);

    // sourceNode 指向下一个节点
    sourceNode = sourceNode->next;
    // targetNode 指向当前节点
    chainNode<T>* targetNode = firstNode;

    // 遍历节点, 直到最后指向 NULL
    while (sourceNode != NULL) {
        // 复制剩余元素
        targetNode->next = new chainNode<T>(sourceNode->element);
        targetNode = targetNode->next;
        sourceNode = sourceNode->next;
    }
    targetNode->next = NULL; // 链表结束
}

// 析构函数
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;

    // 直到当前节点等于 theElement 或者 NULL 时退出
    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;
        // 元素个数减一
        listSize--;
        // 删除 deleteNode 指向的节点
        delete deleteNode;
    }
}

template<class T>
void chain<T>::insert(int theIndex, const T& theElement) {
    // 在索引为 theIndex 的位置上插入元素 theElement
    if (theIndex < 0 || theIndex > listSize) {
        // 无效索引, 即小于 0 和多于当前元素个数
        throw "无效索引";
    }
    else 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;
        }
        // 前驱节点的下一个节点是 theElement
        // theElement 的下一个节点是前驱结点的下一个节点
        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>::checkIndex(int theIndex) const {
    // 确定索引 theIndex 在 0 和 listSize - 1 之间
    if (theIndex < 0 || theIndex >= listSize) {
        throw "无效索引";
    }
}


int main() {
    try {
        chain<int> list(10);
        list.insert(0, 10);
        list.insert(1, 10);
        list.insert(3, 10);
        cout << "原链表: " << list << endl;
        list.erase(1);
        cout << "删除索引为 1 的元素后: " << list << endl;
    }
    catch (const char* c) {
        cout << "捕获异常: " << c << endl;
    }
    return 0;
}

箱子排序

普通排序, 时间复杂度 O(n2)O(n^2) 分数相同的放在同一个箱子里, 再把箱子按顺序连接

image.png

箱子排序不会改变分数相同的节点的相对次序, 因此成为“稳定排序”

步骤

  1. 连续地删除链表首元素并将其插入到相应箱子链表的首部
  2. 逐个删除每个箱子中的元素(从最后一个箱子开始)并将其插入到一个初始为空的链表的首部

代码实现

#include<iostream>
#include<string>
#include<list>
using namespace std;

struct studentRecord {
    int score;
    string name;
    int operator !=(const studentRecord& x) const {
        return score != x.score;
    }
    operator int() const { return score; }
};

ostream& operator << (ostream& out, const studentRecord& x) {
    out << x.score << ' ' << x.name << endl;
    return out;
};

void binSort(list<studentRecord>& theChain, int range) {
    // 按分数排序, 对箱子初始化
    list<studentRecord> *bin;
    bin = new list<studentRecord>[range + 1];

    // 把学生记录从链表取出, 然后分配到箱子里
    size_t numberOfElements = theChain.size();
    for (int i = 1; i <= numberOfElements; i++) {
        studentRecord x = theChain.front(); // 取出头节点
        theChain.pop_front(); // 删除头节点
        bin[x.score].emplace_front(x); // 插入箱子头部
    }

    // 从箱子中收集元素
    for (int j = range; j >= 0; j--) {
        // 遍历箱子中的元素
        while (!bin[j].empty()) {
            studentRecord x = bin[j].front(); // 取出箱子头节点
            bin[j].pop_front();
            theChain.emplace_front(x); // 插入链表头部
        }
    }
    delete[] bin;
}


int main() {
    list<studentRecord> theChain;
    studentRecord x = { 5, "Jane" };
    studentRecord y = { 3, "Ming" };
    studentRecord z = { 2, "May" };
    studentRecord n = { 4, "Quene" };
    theChain.emplace_back(x);
    theChain.emplace_back(y);
    theChain.emplace_back(z);
    theChain.emplace_back(n);

    list<studentRecord>::iterator it = theChain.begin();
    while (it != theChain.end()) {
        cout << *it;
        it++;
    } // 5 3 2 4
    cout << endl;

    // 5 个桶, 代表分数取值 1~5
    binSort(theChain, 5);
    it = theChain.begin();
    while (it != theChain.end()) {
        cout << *it;
        it++;
    } // 2 3 4 5
    cout << endl;

    return 0;
}
  • 第一个 for 循环, 时间复杂度 O(n)O(n)
  • 第二个 for 循环, 时间复杂度 O(n+range)O(n+range)(这里的 n 对应 while 循环)

n: 元素个数
range: 箱子个数

基数排序

把数分解成基数, 如用基数 10 将 213 分解成

2×102+1×10+32×10^2+1×10+3

再对基数 2, 1, 3 进行排序

箱子排序的问题

如果有 1000 个箱子, 但只有 10 个节点需要排序, 那么箱子的初始化需要 1000 步, 节点的分配需要 10 步, 收集节点需要 1000 步, 共 2010 步, 性能差

时间复杂度:

O(2n+range)O(2n+range)

基数排序(多阶段箱子排序)

image.png

步骤
  1. 将数据切成 c 段(由基数 r 决定)
  2. 对每一段进行箱子排序, 每次排序只需要 r 个箱子

箱子初始化 r 步, 节点初始化 n 步, 收集节点 r 步, 共执行 c 次

时间复杂度:

O(c×(2×r+n))O(c×(2×r+n))
代码实现
#include<iostream>
#include<string>
#include<list>
#include <math.h>
using namespace std;

struct studentRecord {
    int score;
    string name;
    int operator !=(const studentRecord& x) const {
        return score != x.score;
    }
    operator int() const { return score; }
};

ostream& operator << (ostream& out, const studentRecord& x) {
    out << x.score << ' ' << x.name << endl;
    return out;
};

void radixSort(list<studentRecord>& theChain, int radix, int c) {
    // 循环 c 次
    for (int i = 1; i <= c; i++) {
        // 箱子初始化
        list<studentRecord>* bin;
        bin = new list<studentRecord>[radix + 1];

        // 把学生记录从链表取出, 然后分配到箱子里
        size_t numberOfElements = theChain.size();

        for (int j = 1; j <= numberOfElements; j++) {
            studentRecord x = theChain.front(); // 取出头节点
            theChain.pop_front(); // 删除头节点
            bin[x.score % (int)pow(radix, i) / (int)pow(radix, i - 1)].emplace_front(x);
        }

        // 从箱子中收集元素
        for (int j = radix; j >= 0; j--) {
            // 遍历箱子中的元素
            while (!bin[j].empty()) {
                studentRecord x = bin[j].front(); // 取出箱子头节点
                bin[j].pop_front();
                theChain.emplace_front(x); // 插入链表头部
            }
        }

        delete[] bin;
    }
}


int main() {
    list<studentRecord> theChain;
    int score[10] = { 5, 109, 123, 340, 20, 890, 560, 198, 87, 100 };
    string name[10] = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J" };
    int len = sizeof(score) / sizeof(score[0]);

    for (int i = 0; i < len; i++) {
        studentRecord x = { score[i], name[i] };
        theChain.emplace_back(x);
    }

    list<studentRecord>::iterator it = theChain.begin();
    while (it != theChain.end()) {
        cout << *it;
        it++;
    }
    cout << endl;

    radixSort(theChain, 10, 3);
    it = theChain.begin();
    while (it != theChain.end()) {
        cout << *it;
        it++;
    }
    cout << endl;

    return 0;
}

其中, 需要注意的是每一次循环, 其箱子的索引为

(x.score%ri)/ri1 (i=1c)(x.score \% r^i) / r^{i-1}\ (i=1\sim c)

顺序表逆序(转置)

要求空间复杂度为 O(1)O(1)
这里直接使用数组代替顺序表

#include<iostream>
using namespace std;

int main() {
    int score[10] = { 5, 109, 123, 340, 20, 890, 560, 198, 87, 100 };
    int len = sizeof(score) / sizeof(score[0]);

    int temp;
    // 从一半处开始前后交换
    for (int i = 0; i < len / 2; i++) {
        temp = score[i];
        score[i] = score[len - i - 1];
        score[len - i - 1] = temp;
    }
    
    // 100 87 198 560 890 20 340 123 109 5 
    for (int i = 0; i < len; i++) {
        cout << score[i] << " ";
    }
    return 0;
}

时间复杂度:

O(3×n2)=O(3n2)=O(n)O\left(3×\frac{n}{2}\right)=O\left(\frac{3n}{2}\right)=O(n)

分段逆序

要求空间复杂度 O(1)O(1) 即将一个顺序表 (a1,a2,,an,b1,b2,,bm)(a_1, a_2, \cdots, a_n, b_1, b_2, \cdots, b_m) 变成 (b1,b2,,bm,a1,a2,,an)(b_1, b_2, \cdots, b_m, a_1, a_2, \cdots, a_n)

步骤

  1. (a1,a2,,an)(a_1, a_2, \cdots, a_n) 变成 (an,,a2,a1)(a_n, \cdots, a_2, a_1)
  2. (b1,b2,,bm)(b_1, b_2, \cdots, b_m) 变成 (bm,,b2,b1)(b_m, \cdots, b_2, b_1)
  3. 整体变成 (an,,a2,a1,bm,,b2,b1)(a_n, \cdots, a_2, a_1, b_m, \cdots, b_2, b_1)
  4. 将整体逆序, 变成 (b1,b2,,bm,a1,a2,,an)(b_1, b_2, \cdots, b_m, a_1, a_2, \cdots, a_n)

代码

#include<iostream>
using namespace std;

int main() {
    int score[10] = { 5, 109, 123, 340, 20, 890, 560, 198, 87, 100 };
    int n = 4;
    int m = 6;
    int len = sizeof(score) / sizeof(score[0]); // 数组长度

    int temp;
    // 前半段逆序
    for (int i = 0; i < n / 2; i++) {
        temp = score[i];
        score[i] = score[n - i - 1];
        score[n - i - 1] = temp;
    }

    // 后半段逆序
    int* s = score + n;
    for (int i = 0; i < m / 2; i++) {
        temp = s[i];
        s[i] = s[m - i - 1];
        s[m - i - 1] = temp;
    }

    // 整体逆序
    for (int i = 0; i < len / 2; i++) {
        temp = score[i];
        score[i] = score[len - i - 1];
        score[len - i - 1] = temp;
    }

    for (int i = 0; i < len; i++) {
        cout << score[i] << " ";
    }
    return 0;
}

时间复杂度:

O(3n2+3m2+3(n+m)2)=O(len)O\left(\frac{3n}{2}+\frac{3m}{2}+\frac{3(n+m)}{2}\right)=O(len)

有序顺序表合并

要求空间复杂度 O(1)O(1) 即一个顺序表 (a1,a2,,an,b1,b2,,bm)(a_1, a_2, \cdots, a_n, b_1, b_2, \cdots, b_m), 其中 (a1,a2,,an)(a_1, a_2, \cdots, a_n) 是升序的, (b1,b2,,bm)(b_1, b_2, \cdots, b_m) 也是升序的, 需要将整个列表变成升序的

步骤

  1. 判断 a[i] 是否比 b[j] 大, 如果 a[i] > b[j], 两者交换
  2. 由于 a[i+1]~a[n] 都是比 a[i] 大的, 所以肯定不能仅仅把 a[i] 换到 b[j] 的位置, 还要将 a[i] 重新移回 a[i+1] 的前面, 也就是 b[j] 的后一位

image.png

代码

#include<iostream>
using namespace std;

void swap(int* a, int* b) {
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int score[10] = { 5, 109, 123, 340, 20, 110, 260, 698, 787, 800 };
    int len = sizeof(score) / sizeof(score[0]);

    int i = 0, j = 4; // 两个子序列的起始位置
	
    while (j < len && i < j) {
        if (score[i] <= score[j]) {
            // 如果 a[i] <= b[j], 则下一步 a[i+1] 和 b[j] 的对比, 以此类推
            i++;
        }
        else {
            // 如果 a[i] > b[j], 交换 a[i] 和 b[i]
            // ... a[i] a[i+1].... b[j] ... => ... b[j] a[i+1] .... a[i] ...
            swap(score[i], score[j]);

            // 将 a[i] 移动到 a[i+1] ~ a[n] 之前
            // 即 ... b[j] a[i] a[i+1] .......
            for (int k = j - 1; k > i; k--) {
                swap(score[k], score[k + 1]);
            }
            i++;
            j++;
        }
    }
	
    for (int i = 0; i < len; i++) {
        cout << score[i] << " ";
    }
    return 0;
}