【线性表】——双向链表实现线性表

一、概述


数据结构必知

  • 数据结构的逻辑结构分为集合结构、线性结构、树形结构、图形结构。
  • 数据结构的存储结构分为顺序结构、链式结构、索引结构、散列结构。
  • 常见的八大数据结构有数组(Array)、栈(Stack)、队列(Queue)、链表(Linked List)、树(Tree)、图(Graph)、散列表(Hash)。

关注点

  • 如何方便的存储(增删改查、遍历、排序)数据:有序集合(List)、无序集合(Set)。
  • 如何约束数据的线性关系:栈(Stack,先进后出)、队列(先进先出)。
  • 如何表示多个数据之间的关系:树(Tree,一对多)、图(Graph,多对多)。
  • 如何存储键值类型的数据对:映射(Map)。
  • 如何提高性能:集合容器本身需要根据数据量的多少而选择合适的数据结构。



二、简单线性表


知其然知其所以然。

像Java的集合框架、C++的STL的容器都非常好用,实现简单的线性表也仅仅是了解,最重要的还是要熟练API的使用。


数组和链表的关系

编程语言诞生之初,早已经有了指针和内存······

  • 数组:指针指向了一块若干数据大小的连续内存,可以通过指针或下标去访问。
  • 链表: 指针指向了一块1个数据大小的内存并且这个内存中保存着指向另一块1个数据大小的内存的指针,只能通过指针去访问。

头节点的作用

链表有无头节点

设置头节点只是为了操作的方便。如果不设置头节点,从图中例子的对比可以看到:处理第一个元素是比较麻烦的。

  • 如果采用指针传参(地址值的值传递),指针类型的实参不可能在函数内部指向另一个地址。

  • 如果采用引用传参(引用的本质是常指针),引用只是变量的别名,在申明时就已经确定了指向并且永远不能更改其指向。

    int a = 5;
    int& ref = a;
    
    // 等价于
    int* const p = &a;
    

通常的解决方案有:

  • 将在函数内改变指向的指针作为返回值返回,在调用时更新指针。(如图中右侧示例,并且这也仅仅是为了处理第一个元素的问题而不得不这么做)

  • 采用二级指针传参,将指针的地址值传入函数即可以在函数内改变指针的指向。

    Node** headContainer = &head;
    
  • 将头指针设为全局变量。(换言之,将头指针作为一个对象的属性会不会更好)


约定线性表结构

注:Java和C++的API中的结构并非这样,这样约定的目的仅仅在于方便。
在这里插入图片描述

1、有序集合(List)接口——list.h
#ifndef DEMO_TEST_LIST_H
#define DEMO_TEST_LIST_H

#include <string>
#include <iostream>

template<typename E>
class List {
public:
    virtual ~List();

    virtual int size() = 0;
    
    virtual bool isEmpty() = 0;
    
    /**
     * 向表中添加元素
     * @param element
     */
    virtual void add(E element) = 0;
    
    /**
     * 向表中指定位置插入元素
     * @param index
     * @param element
     */
    virtual void add(int index, E element) = 0;
    
    /**
     * @param index
     * @return 表中指定位置的元素
     */
    virtual E get(int index) = 0;
    
    /**
     * @param element
     * @return 获取某元素在表中的下标
     */
    virtual int indexOf(E element) = 0;
    
    /**
     * 更改表中指定位置的元素
     * @param index
     * @param element
     */
    virtual void set(int index, E element) = 0;
    
    /**
     * 移除表中某个元素
     * @param element
     */
    virtual void removeByElement(E element) = 0;
    
    /**
     * 移除表中指定下标的元素
     * @param index
     */
    virtual void removeByIndex(int index) = 0;
    
    /**
     * 移除表中全部元素
     */
    virtual void removeAll() = 0;
    
    virtual std::string toString() = 0;
};

template<typename T>
List<T>::~List() {
    std::cout << "默认析构函数";
}

#endif //DEMO_TEST_LIST_H
2、队列(Queue)接口——queue.h
#ifndef DEMO_TEST_QUEUE_H
#define DEMO_TEST_QUEUE_H

template<typename E>
class Queue {
public:
    /**
     * 将指定的元素插入此队列
     * @return 是否插入成功
     */
    virtual bool offer(E element) = 0;

    /**
     * 获取队列的头但不移除
     * @return 队列的头
     */
    virtual E peek() = 0;
    
    /**
     * 获取队列的头并移除
     * @return 队列的头
     */
    virtual E poll() = 0;
};

#endif //DEMO_TEST_QUEUE_H
3、栈(Stack)接口——stack.h
#ifndef DEMO_TEST_STACK_H
#define DEMO_TEST_STACK_H

template<typename E>
class Stack {
public:
    /**
     * 压栈
     * @param element
     */
    virtual void push(E element) = 0;

    /**
     * 获取栈顶元素但不出栈
     * @return 栈顶元素
     */
    virtual E peek() = 0;
    
    /**
     * 获取栈顶元素并出栈
     * @return 栈顶元素
     */
    virtual E pop() = 0;
};

#endif //DEMO_TEST_STACK_H



三、双向链表实现线性表


1、双向链表图示

注:和图中不完全相符,源代码的链表用firstlast表示首尾元素的指针(也即head指针和tail指针);节点用prevnext表示前驱和后继指针。

双向链表图示

2、add()算法图示

add()算法

3、remove()算法图示

remove()算法

4、节点(Node)源代码——node.h
#ifndef DEMO_TEST_NODE_H
#define DEMO_TEST_NODE_H


template<typename E>
struct Node {
    E data;

    Node<E>* prev;
    Node<E>* next;
};

#endif //DEMO_TEST_NODE_H
5、双向链表(LinkedList)源代码——linked_list.h
#ifndef DEMO_TEST_LINKED_LIST_H
#define DEMO_TEST_LINKED_LIST_H

#include "node.h"
#include "list.h"
#include "stack.h"
#include "queue.h"

// 实现toString()需要用到
using namespace std;

template<typename E>
class LinkedList : public List<E>, public Stack<E>, public Queue<E> {
private:
    int length;
    Node<E>* first;
    Node<E>* last;

public:
    LinkedList() : length(0), first(nullptr), last(nullptr) {}

    int size() override {
        return this->length;
    }

    bool isEmpty() override {
        return this->length == 0;
    }

    void add(E element) override {
        // 新建节点
        Node<E>* node = new Node<E>();
        node->data = element;
        node->next = nullptr;

        if (this->first == nullptr) {
            // 空链表
            node->prev = nullptr;

            this->first = node;
            this->last = node;
        } else {
            // 非空链表
            node->prev = this->last;
            this->last->next = node;

            this->last = node;
        }
        this->length++;
    }

    void add(int index, E element) override {
        if (index > this->length - 1) {
            // 下标越界
            throw "Index Out Of Bounds";
        }
        // 新建节点
        Node<E>* node = new Node<E>();
        node->data = element;

        if (index == 0) {
            // 插入到第一个位置
            node->prev = nullptr;
            node->next = this->first;
            this->first->prev = node;

            this->first = node;
        } else if (index == this->length - 1) {
            // 插入到最后一个位置
            node->next = nullptr;
            node->prev = this->last;
            this->last->next = node;

            this->last = node;
        } else {
            // 寻找插入位置
            Node<E>* toInsert = nullptr;
            if (index <= this->length / 2) {
                Node<E>* p = this->first->next;
                int i = 1;
                while (i != index) {
                    i++;
                    p = p->next;
                }
                toInsert = p;
            } else if (index > this->length / 2) {
                Node<E>* p = this->last->prev;
                int i = this->length - 2;
                while (i != index) {
                    i--;
                    p = p->prev;
                }
                toInsert = p;
            }
            // 此处toInsert必定不为空指针
            Node<E>* before = toInsert->prev;
            before->next = node;
            node->prev = before;

            node->next = toInsert;
            toInsert->prev = node;
        }
        this->length++;
    }


    E get(int index) override {
        if (index > this->length - 1) {
            // 下标越界
            throw "Index Out Of Bounds";
        }
        Node<E>* toGet = nullptr;
        if (index <= this->length / 2) {
            // 从前向后查询
            Node<E>* p = this->first;
            int i = 0;
            while (i != index) {
                i++;
                p = p->next;
            }
            toGet = p;
        } else if (index > this->length / 2) {
            // 从后向前查询
            Node<E>* p = this->last;
            int i = this->length - 1;
            while (i != index) {
                i--;
                p = p->prev;
            }
            toGet = p;
        }
        return toGet->data;
    }

    int indexOf(E element) override {
        Node<E>* p = this->last;
        int i = this->length - 1;
        while (p != nullptr) {
            if (p->data == element) {
                break;
            }
            i--;
            p = p->prev;
        }
        return i;
    }

    void set(int index, E element) override {
        Node<E>* toSet = nullptr;
        if (index > this->length - 1) {
            // 下标越界
            throw "Index Out Of Bounds";
        } else if (index <= this->length / 2) {
            // 从前向后查询
            Node<E>* p = this->first;
            int i = 0;
            while (i != index) {
                i++;
                p = p->next;
            }
            toSet = p;
        } else if (index > this->length / 2) {
            // 从后向前查询
            Node<E>* p = this->last;
            int i = this->length - 1;
            while (i != index) {
                i--;
                p = p->prev;
            }
            toSet = p;
        }
        // 此处toSet必定不会为空指针
        toSet->data = element;
    }

    void removeByElement(E element) override {
        Node<E>* toRemove = nullptr;

        // 查询待删除的节点
        Node<E>* p = this->first;
        while (p != nullptr) {
            if (p->data == element) {
                toRemove = p;
                break;
            }
            p = p->next;
        }
        if (toRemove != nullptr) {
            // 如果查询到先连接待删除节点的前驱和后继,然后释放待删除节点的内存
            if (toRemove == this->first) {
                // 待删除的是第一个元素
                Node<E>* after = toRemove->next;
                after->prev = nullptr;

                this->first = after;
            } else if (toRemove == this->last) {
                // 待删除的是最后一个元素
                Node<E>* before = toRemove->prev;
                before->next = nullptr;

                this->last = before;
            } else {
                // 待删除的是中介的元素
                Node<E>* before = toRemove->prev;
                Node<E>* after = toRemove->next;

                before->next = after;
                after->prev = before;
            }
            delete toRemove;
            this->length--;
        }
    }

    void removeByIndex(int index) override {
        Node<E>* toRemove = nullptr;

        //查询待删除的节点
        if (index > this->length - 1) {
            // 下标越界
            throw "Index Out Of Bounds";
        } else if (index <= this->length / 2) {
            // 从前向后查询
            Node<E>* p = this->first;
            int i = 0;
            while (i != index) {
                i++;
                p = p->next;
            }
            toRemove = p;
        } else if (index > this->length / 2) {
            // 从后向前查询
            Node<E>* p = this->last;
            int i = this->length - 1;
            while (i != index) {
                i--;
                p = p->prev;
            }
            toRemove = p;
        }
        if (toRemove != nullptr) {
            // 如果查询到先连接待删除节点的前驱和后继,然后释放待删除节点的内存
            if (toRemove == this->first) {
                // 待删除的是第一个元素
                Node<E>* after = toRemove->next;
                after->prev = nullptr;

                this->first = after;
            } else if (toRemove == this->last) {
                // 待删除的是最后一个元素
                Node<E>* before = toRemove->prev;
                before->next = nullptr;

                this->last = before;
            } else {
                // 待删除的是中介的元素
                Node<E>* before = toRemove->prev;
                Node<E>* after = toRemove->next;

                before->next = after;
                after->prev = before;
            }
            delete toRemove;
            this->length--;
        }
    }

    void removeAll() override {
        Node<E>* p = this->first;
        while (p != nullptr) {
            Node<E>* temp = p->next;
            delete p;
            if (temp != nullptr) {
                p = temp;
            } else {
                break;
            }
        }
        this->first = nullptr;
        this->last = nullptr;
        this->length = 0;
    }


    std::string toString() override {
        std::string left = "{";
        std::string right = "}";
        std::string prop1 = "\"length\": \"" + to_string(this->length).append("\"");

        std::string prop2 = "\"elements\": [";
        Node<E>* p = this->first;
        while (p != nullptr) {
            prop2.append("\"")
                 .append(to_string(p->data))
                 .append("\"");
            if (p->next != nullptr) {
                prop2.append(", ");
            }
            p = p->next;
        }
        prop2.append("]");

        return left.append(prop1)
                   .append(", ")
                   .append(prop2)
                   .append(right)
                   .append("\n");
    }

    bool offer(E element) override {
        // 将表头作为队头
        this->add(0, element);
        return true;
    }

    E peek() override {
        // 将表尾作为栈顶和队尾
        return this->last->data;
    }

    E poll() override {
        // 将表为作为队尾
        E result = this->last->data;
        this->removeByIndex(this->length - 1);
        return result;
    }

    void push(E element) override {
        // 将表尾作为栈顶
        this->add(element);
    }


    E pop() override {
        // 将表尾作为栈顶
        E result = this->last->data;
        this->removeByIndex(this->length - 1);
        return result;
    }
};

#endif //DEMO_TEST_LINKED_LIST_H



四、数组实现线性表


// 数组的增删改查、遍历、排序




五、测试


1、测试文件——main.cpp
#include <iostream>

#include <ctime>
#include <iostream>
#include "linked_list.h"

using namespace std;

// 测试add()
void init(List<int>* list) {
    srand(time(NULL));
    for (int i = 0; i < 10; i++) {
        list->add(rand() % 100);
    }
    cout << list->toString() << endl;

    list->add(0, 99);
    cout << "add(0, 99)\n" << list->toString() << endl;

    list->add(5, 999);
    cout << "add(5, 999)\n" << list->toString() << endl;

    list->add(9, 9999);
    cout << "add(9, 9999)\n" << list->toString() << endl;
}

// 测试removeByElement()
void test1(List<int>* list) {
    list->removeByElement(99);
    list->removeByElement(999);
    list->removeByElement(9999);
    cout << "removeByElement 99, 999, 9999\n" << list->toString() << endl;
}

// 测试removeByIndex()
void test2(List<int>* list) {
    list->removeByIndex(0);
    cout << "removeByIndex(0)\n" << list->toString() << endl;

    list->removeByIndex(5);
    cout << "removeByIndex(5)\n" << list->toString() << endl;

    list->removeByIndex(9);
    cout << "removeByIndex(9)\n" << list->toString() << endl;
}

// 测试removeAll()
void test3(List<int>* list) {
    list->removeAll();
    cout << "removeAll()\n" << list->toString() << endl;
}

// 测试set()
void test4(List<int>* list) {
    list->set(0, 77);
    list->set(5, 777);
    list->set(9, 7777);
    cout << "set(0, 77), set(5, 777), set(9, 7777)\n" << list->toString() << endl;
}

// 测试indexOf()
void test5(List<int>* list) {
    int a = list->indexOf(99);
    int b = list->indexOf(999);
    int c = list->indexOf(9999);
    cout << "indexOf(99)=" << a << ", indexOf(999)=" << b << ", indexOf(9999)=" << c << endl;
}


int main() {
    List<int>* list = new LinkedList<int>();
    init(list);

//    test1(list);
//    test2(list);
//    test3(list);
//    test4(list);
//    test5(list);

    return 0;
}
2、测试截图

test1
test2
test3
test4
test5



六、参考资料


原创文章 41 获赞 34 访问量 3万+

猜你喜欢

转载自blog.csdn.net/XY1790026787/article/details/89951198