【DS】第二章-顺序表和链表

第二章 顺序表和链表

顺序表

什么是顺序表

  顺序表是物理地址全部连续的存储数据的方式。顺序表分为动态顺序表以及动态的顺序表,静态的顺序表一般很少使用,因为其大小一旦固定不能再进行改变。
  顺序表在开发中十分经常使用,因为其方便简单,并且易于操作。数组就是顺序表的一种,因为其在逻辑结构上是线性的,在物理结构上是连续的。由于十分常用在Cpp的STL库中封装了以顺序表为数据结构的vector容器供我们使用。

实现

  顺序表的实现十分类似于vector类的实现。

#include <iostream>
#include <stdlib.h>
#include <assert.h>
#include <stdlib.h>
#include <Windows.h>
using namespace std;
#define N 100
//顺序表的实现
//十分类似于我们实现过的vector类,毕竟vector就是顺序表
template<class T>
class SeqList
{
public:
    //构造函数
    SeqList()
    :_array(nullptr)
    ,_size(0)
    ,_capacity(0)
    {}
    //析构函数
    ~SeqList()
    {
        if(_array != nullptr)
        {
            delete _array;
        }
    }
    //pos前插入
    void Insert(size_t pos, const T& value)
    {
        //判断pos是否合法
        if(pos > _size || pos < 0)
        {
            return;
        }
        //扩容
        Expend();
        for(size_t i = _size; i > pos; i--)
        {
            _array[i] = _array[i - 1];
        }
        _array[pos] = value;
        _size++;
    }
    //尾插
    void Push_back(const T& value)
    {
        //扩容
        Expend();
        _array[_size] = value;
        _size++;
    }
    //头插
    void Push_front(const T& value)
    {
        //扩容
        Expend();
        //所有元素向后挪一个位置
        for(size_t i = _size; i > 0; i--)
        {
            _array[i] = _array[i - 1];
        }
        _array[0] = value;
        _size++;
    }
    //尾删
    void Pop_back()
    {
        //删除前要先判断,有元素才能删
        if(_size > 0)
        {
            _size--;
        }
    }
    //头删
    void Pop_front()
    {
        //有元素才能删
        if(_size > 0)
        {
            for (size_t i = 1; i < _size; i++)
            {
                _array[i - 1] = _array[i];
            }
            _size--;
        }
    }
    //删除pos当前位置数据
    void Erase(size_t pos)
    {
        //pos不合法
        if(pos >= _size || pos < 0)
        {
            return;
        }
        for(size_t i = pos; i < _size - 1; i++)
        {
            _array[pos] = _array[pos + 1];
        }
        _size--;
    }
    //查找
    size_t Find(const T& value)
    {
        for(size_t i = 0; i < _size; i++)
        {
            if(_array[i] == value)
            {
                return i;
            }
        }
        return -1;
    }
    //二分查找
    size_t BinaryFind(const T& value)
    {
        //左闭右开区间
        size_t high = _size, low = 0;
        while(high > low)
        {
            size_t mid = (high + low) / 2;
            if(_array[mid] == value)
            {
                return mid;
            }
            else if(_array[mid] > value)
            {
                high = mid;    
            }
            else
            {
                low = mid + 1;
            }
        }
    }
    //修改
    void Modify(size_t pos, const T& value)
    {
        if(pos < 0 || pos >= _size)
        {
            return;
        }
        _array[pos] = value;
    }
    //打印
    void Print()
    {
        for(size_t i = 0; i < _size; i++)
        {
            cout << _array[i] << " ";
        }
        cout << endl;
        //cout << _size << endl;
    }
    //当前元素个数
    size_t Size()
    {
        return _size;
    }
    //某个位置的值
    T& operator[](size_t pos)
    {
        assert(pos >=0 && pos < _size);
        return _array[pos];
    }
    //冒泡排序
    void BubbleSort()
    {
        for(int i = 0; i < _size - 1; i++)
        {
            bool flag = false;
            for(int j = 0; j < _size - i - 1; j++)
            {
                if(_array[j] > _array[j + 1])
                {
                    swap(_array[j], _array[j + 1]);
                    flag = true;
                }
            }
            if(flag == false)
            {
                break;
            }
        }
    }
    void RemoveAll()
    {
        _size = 0;
    }
private:
    //T _array[N];//静态顺序表,利用数组,不可变,十分不灵活
    T* _array;//动态顺序表,利用指针动态开辟
    size_t _size;//长度
    size_t _capacity;//容量
    //扩容
    void Expend()
    {
        if(_size == _capacity)//满了
        {
            size_t newCapacity = (_capacity == 0 ? 5 : 2 * _capacity);
            //创建更大空间,拷贝,释放原有空间
            _array = (T*)realloc(_array, newCapacity * sizeof(T));
            //申请失败
            assert(_array);
            //更新容量
            _capacity = newCapacity;
        }
    }
};

顺序表的优缺点

  顺序表优点
  1、根据下标随机访问时间复杂度O(1)。
  2、不会产生内存碎片。
  3、代码简单。
  4、在尾插时事件复杂度为O1
  顺序表缺点:
  1、在中间插入时时间复杂度为On,最坏情况下要移动整个顺序表完成头插。
  2、增容申请新空间进行数据拷贝再释放旧空间,有这不小消耗。
  3、增容一次空间为原有空间两倍,可能会造成空间大量浪费。

链表

什么是链表

  顺序表是物理地址连续的数据存储方式,而链表与之相反,链表存储数据的物理地址不一定连续,因此它不能像顺序表那样通过直接寻址的方式访问到数据,它通过指针来连接和组织数据,因此也使得它和顺序表有着截然不同的特征,并且相比顺序表来说或许更难理解一些。
链表
  链表有着以下几种种类:
  1、带头链表,不带头链表。
  2、单向链表,双向链表。
  3、循环链表,不循环链表。
  他们组合搭配起来一共有8种组合,
  由于链表的特性它可以很好地结局一些顺序表的缺点,比如他不需要扩容,并且更方便插入等等,但是在一些方面上它也有着不如顺序表的地方,关于优缺点我们放在最后再进行分析,接下来实现一个简单的带头单向不循环链表

实现

  在Cpp的STL中有一个list容器,其中实现了一个带头双向循环链表,这里我们简化进行实现带头单向不循环链表,思路更加简单(带头双向循环链表在Cpp章节中在模拟实现list时也有实现)。

#include <iostream>
using std::cout;
using std::endl;
template<class T>
struct ListNode
{
    //构造函数
    ListNode(const T& value = T())
        :data(value)
        ,next(nullptr)
    {

    }
    T data;//数据域
    ListNode* next;//指针域,指向下一个节点
};
template<class T>
class List
{
public:
    //构造函数
    List()
        :_head(new ListNode<T>)
        ,_size(0)
    {
    }
    ~List()
    {
        //依次删除所有节点以释放所有空间
        while(_size > 0)
        {
            Pop_Front();
        }
        delete _head;
    }
    //头插
    void Push_Front(const T& value)
    {
        InsertPos(0, value);
    }
    //尾插
    void Push_Back(const T& value)
    {
        InsertPos(_size, value);
    }
    //打印所有数据
    void PrintAll()
    {
        ListNode<T>* temp = _head->next;
        while(temp != nullptr)
        {
            cout << temp->data << " ";
            temp = temp->next;
        }
        cout << endl;
    }
    //在下标为pos的元素前进行插入
    void InsertPos(size_t pos, const T& value)
    {
        if(pos < 0 || pos > _size)
        {
            cout << "InsertPos: pos error" << endl;
            return;
        }
        //这里的插入时间复杂度本应是O1,但是由于链表不便于寻址因此又需要线性的时间进行寻址
        ListNode<T>* node = FindForPos(pos);
        if(node == nullptr)
        {
            return;
        }
        ListNode<T>* newNode = new ListNode<T>(value);
        newNode->next = node->next;
        node->next = newNode;
        _size++;
    }
    //删除下标为pos的元素
    void ErasePos(size_t pos)
    {
        if(pos < 0 || pos >= _size)
        {
            cout << "ErasePos: pos error" << endl;
            return;
        }
        ListNode<T>* node = FindForPos(pos);
        if(node == nullptr)
        {
            return;
        }
        ListNode<T>* temp = node->next;
        node->next = temp->next;
        delete temp;
        _size--;
    }
    //尾删
    void Pop_Back()
    {
        ErasePos(_size - 1);
    }
    //头删
    void Pop_Front()
    {
        ErasePos(0);
    }
    //返回链表长度
    size_t Size()
    {
        return _size;
    }
private:
    //返回下标为pos的元素的前一个元素,下标为0的元素则返回头节点
    ListNode<T>* FindForPos(size_t pos)
    {
        ListNode<T> *temp = _head;
        for(int i = 0; i < pos; i++)
        {
            //不存在,长度不够
            if(temp == nullptr)
            {
                cout << "FindForPos: pos error" << endl;
                return nullptr;
            }
            temp = temp->next;
        }
        return temp;
    }
private:
    ListNode<T>* _head;
    size_t _size;
};

链表的优缺点

  链表的优点:
  1、链表在任意位置插入和删除时时间复杂度都能达到O1
  2、插入一个节点开辟一个空间,不牵扯扩容问题。
  链表的缺点:
  1、以节点为单位存储数据,并且还要存储指针可能会浪费更多空间。
  2、不支持随机访问,因此在某一位置插入尽管插入只需要O1但是找到这个位置需要On
  3、思路实现略复杂。

链表反转问题

  给一个单链表,反转这个单链表。
  https://leetcode-cn.com/problems/reverse-linked-list/description/
  反转链表有很多种方式,我们可以尾删所有结点并同时将他们重新头插回链表,但这样的思路消耗实在太高,我们每次尾插都不得不遍历整张单链表去找到尾,因此对其简化,这里提供两种思路,
  第一种,后插头删法。这种方法是通过从第二个节点开始遍历整个链表,依次将节点移向头部的方法。图解如下:
反转链表
  题解:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == NULL)
        {
            return head;
        }
        ListNode* oldHead = head;
        ListNode* temp = oldHead->next;
        while(oldHead->next != NULL)
        {
            oldHead->next = temp->next;
            temp->next = head;
            head = temp;
            temp = oldHead->next;
        }
        return head;
    }
};

  2、第二种:向后转法。第二种思路更为直观,我们就直接将每个结点中的next的指向改变即可。
反转链表
  题解:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == NULL)
        {
            return head;
        }
        ListNode* prev = head;
        ListNode* cur = head->next;
        ListNode* next = cur;
        head->next = NULL;
        while(cur != NULL)
        {
            next = cur->next;
            cur->next = prev;
            prev = cur;
            cur = next;
        }
        return prev;
    }
};

  链表有多种多样的问题,我会另开章节逐个归纳,就不再此继续罗列了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值