zoukankan      html  css  js  c++  java
  • 数据结构整理(一) —— 链表的各种操作

        马上要面临大规模的面试了,用了太久标准库,已经对数据结构的内部实现快忘了,趁着还有几天时间,自己又回忆了一下,用C++实现出来。所以接下来我在博客中会写一个“数据结构整理”系列,在面试之前,能回忆多少算多少吧,希望面试官能感受到我曾经是一个对数据结构很熟悉的人。。。

        下面进入正题。

        链表和数组都是线性表,数组就不多说了,说一下链表的实现。链表的名字很形象,就是一个一个的结点链在一起,必须找到一个结点的父节点,才能找到该结点,链表一般都有一个头结点,而且头结点一般不保存用户的数据,只是为了方便对链表的寻址。另外,可以在头结点中保存一些额外的信息,例如链表的长度,不过为了可读性,我个人不建议这样做。

        下面是链表中一个结点的一般结构:

    template <typename T>
    class Node {
    public:
        T data;
        Node *next;
        Node(T d) {
            data = d;
            next = nullptr;
        }
        Node() {
            next = nullptr;
        }
    };

        data字段保存结点中需要存储的信息,next指针保存下一个结点的地址。

        一般在链表中都有一个头指针和一个尾指针,头指针始终指向头结点,作用是方便对链表寻址。尾指针指向链表最后一个结点,作用是减少插入数据时消耗的时间。对链表的操作一般有获取长度,插入结点,删除结点,排序,逆置等,所以链表的一般结构如下:

     1 template <typename T>
     2 class LinkList {
     3 private:
     4     Node<T>* head;
     5     Node<T>* rear;
     6     int lenth;
     7 public:
     8     LinkList();
     9     ~LinkList();
    10     inline int size();
    11     void insert(T data);
    12     void remove(T data);
    13     void sort();
    14     void reverse();
    15     void getArray(T*& arr);
    16 };

        一个链表初始只有一个头结点,头指针和尾指针都指向头结点,链表长度为0。另外由于每个结点都是动态申请的堆内存,在析构函数中应及时释放,所以下面是构造函数和析构函数的实现:

     1 template <typename T>
     2 LinkList<T>::LinkList() {
     3     head = new Node<T>();
     4     head->next = nullptr;
     5     rear = head;
     6     lenth = 0;
     7 }
     8 
     9 template <typename T>
    10 LinkList<T>::~LinkList() {
    11     Node<T>* cur = head->next;
    12     while(cur != nullptr) {
    13         delete head;
    14         head = cur;
    15         cur = cur->next;
    16     }
    17 }
    构造函数和析构函数

        向链表中插入数据即把新结点添加到链表末尾,所以利用尾指针可以很方便的做到。删除数据时先将待删除结点的父节点next的指针指向待删除结点的子节点,然后释放掉待删除结点的内存。下面是链表结点的插入和删除操作的实现:

     1 template <typename T>
     2 void LinkList<T>::insert(T data) {
     3     Node<T>* cur = new Node<T>(data);
     4     rear->next = cur;
     5     rear = cur;
     6     lenth++;
     7 }
     8 
     9 template <typename T>
    10 void LinkList<T>::remove(T data) {
    11     Node<T>* before = head;
    12     Node<T>* cur = head->next;
    13     while(cur != nullptr) {
    14         if(cur->data == data) {
    15             before->next = cur->next;
    16             delete cur;
    17             cur = before->next;
    18             lenth--;
    19         } else {
    20             before = cur;
    21             cur = cur->next;
    22         }
    23     }
    24 }
    链表结点的插入和删除

        链表的排序复杂度很高,一般不建议对存在大量数据的链表执行排序操作,链表的排序是一个对链表重建的过程,每次拆下一个结点,按照一定的顺序重新组合回去。注意排序完成后,尾指针一定要指向链表的最后一个结点。下面是链表排序的实现:

     1 template <typename T>
     2 void LinkList<T>::sort() {
     3     Node<T>* p = head->next;
     4     head->next = nullptr;
     5     rear = head;
     6     Node<T>* before = head;
     7     Node<T>* after = head->next;
     8 
     9     while(p != nullptr) {
    10         if(after == nullptr ||
    11             (p->data >= before->data && p->data <= after->data)) {
    12             before->next = p;
    13             p = p->next;
    14             before = before->next;
    15             before->next = after;
    16             before = head;
    17             after = head->next;
    18             if(rear->next != nullptr) {
    19                 rear = rear->next;
    20             }
    21         } else {
    22             before = before->next;
    23             after = after ->next;
    24         }
    25     }
    26 }
    链表的排序

        链表的逆置也是一个重建链表的过程,即改变每个结点的指向,但是头结点依然是头结点。显然,逆置操作之后,尾指针应指向逆置前的第一个数据结点(即头指针的子结点)。下面是逆置操作的实现:

     1 template <typename T>
     2 void LinkList<T>::reverse() {
     3     if(lenth < 2) {
     4         return;
     5     }
     6     rear = head->next;
     7     Node<T>* before = head->next;
     8     Node<T>* after = before->next;
     9     head->next = nullptr;
    10     while(before != nullptr) {
    11         before->next = head->next;
    12         head->next = before;
    13         before = after;
    14         if(after != nullptr) {
    15             after = after->next;
    16         }
    17     }
    18 }
    链表的逆置

        还有头文件中的另外两个函数,获取长度和转化为数组。获取长度很简单,在插入和删除时维护长度字段即可,为了提高在for循环中使用size()函数的效率,声明成inline的。转化为数组即遍历链表,把数据依次插入数组中即可。注意,C++不应返回一个函数中局部变量的数组,因为函数返回后栈被释放,非常危险。下面是获取长度和转化为数组的实现:

     1 template <typename T>
     2 int LinkList<T>::size() {
     3     return lenth;
     4 }
     5 
     6 template <typename T>
     7 void LinkList<T>::getArray(T*& arr) {
     8     arr = new int[lenth];
     9     Node<T>* cur = head->next;
    10     for(int i = 0; cur != nullptr; i++) {
    11         arr[i] = cur->data;
    12         cur = cur->next;
    13     }
    14 }
    获取长度和转化为数组

       最后附上单元测试的代码:

     1 void test_linklist() {
     2     int number[] = {4, 2, 6, 7, 1, 4, 7};
     3     LinkList<int>* list = new LinkList<int>();
     4     for(int i = 0; i < 7; i++) {
     5         list->insert(number[i]);
     6     }
     7 
     8     int* arr = nullptr;
     9     list->getArray(arr);
    10     cout << "init:" << endl;
    11     for(int i = 0; i < list->size(); i++) {
    12         cout << arr[i] << endl;
    13     }
    14     delete arr;
    15 
    16     list->sort();
    17     list->getArray(arr);
    18     cout << "after sort:" << endl;
    19     for(int i = 0; i < list->size(); i++) {
    20         cout << arr[i] << endl;
    21     }
    22     delete arr;
    23 
    24     list->reverse();
    25     list->getArray(arr);
    26     cout << "after reverse:" << endl;
    27     for(int i = 0; i < list->size(); i++) {
    28         cout << arr[i] << endl;
    29     }
    30     delete arr;
    31 
    32     list->remove(4);
    33     list->getArray(arr);
    34     cout << "after remove 4:" << endl;
    35     for(int i = 0; i < list->size(); i++) {
    36         cout << arr[i] << endl;
    37     }
    38     delete arr;
    39 
    40     delete list;
    41 }
    单元测试
  • 相关阅读:
    CodeForces 687B Remainders Game
    CodeForces 689D Friends and Subsequences
    CSU 1810 Reverse
    生成树收录
    吃奶酪
    带逆向思维的并查集
    中位数定理
    种类并查集(关押犯人)
    带权并查集
    分层图
  • 原文地址:https://www.cnblogs.com/wolfred7464/p/4335298.html
Copyright © 2011-2022 走看看