算法未动 粮草先行(基础篇一---链表)

本文深入探讨数据结构核心概念,解析顺序表、链表、双向链表及循环链表原理与应用,强调数据结构与算法之间的紧密联系。

基础数据结构

为什么会从算法联想到数据结构?

因为特定的算法需要在相应的数据结构基础上运行才能发挥良好的性能。

数据结构是什么?

数据结构是数据的组织形式和载体。

算法是什么?

算法是良好的操作步骤(可以看作套路)
综上 学好数据结构是学好算法的前提

1.1 顺序表

最基础最先接触的就是线性表
线性表的定义是什么?
n个数据元素的有限序列,其中n(n>0)表示线性表的长度。
顺序表的定义
顺序表是最基础的数据结构之一,就是线性表的一种存储表现形式,在计算机中表示为一块连续的内存空间 一般特性为:

  1. 顺序表的内存空间是一连串连续的地址空间
  2. 需要有一个唯一的表名来表示
  3. 数据在顺序表中按顺序排列,存取时可以根据相对位置进行随机存取
const int defaultSize = 10;
template <typename DataType> class SeqList
{
public:
	//构造函数
	SeqList (int size = defaultSize){
		if (size > 0)//检查赋予的顺序表大小,如果合法则分配相应大小的内存空间
		{
			maxSize = size;
			elements = new DataType[maxSize];//分配内存大小
		}
	}
	//析构函数
	~SeqList()
	{
		delete[] elements;//回收内存空间
	}
private:
	DataType *elements;
	int maxSize;
}

顺序表的基本操作
顺粗表主要有插入元素、删除元素、查找元素、获取元素和修改元素五个基本操作。

const int defaultSize = 10;
template <typename DataType> class SeqList
{
public:
	//构造函数
	SeqList (int size = defaultSize){
		if (size > 0)//检查赋予的顺序表大小,如果合法则分配相应大小的内存空间
		{
			maxSize = size;
			elements = new DataType[maxSize];//分配内存大小
		}
	}
	//析构函数
	~SeqList()
	{
		delete[] elements;//回收内存空间
	}
	bool insertElement(DataType data);//向表尾插入新元素
	bool deleteElement(int location);//删除指定位置的元素
	DataType getElement(int location);//返回指定位置的元素
	bool changeElement(int location,DataType newData);//修改指定位置的元素
private:
	DataType *elements;
	int maxSize;//顺序表最大大小
	int length;//顺序表的有效长度
}

顺序表的插入操作
插入操作可以划分为两个步骤

  1. 检查顺序表是否已满 如果已满就拒绝此次插入操作
  2. 如果未满 则将新元素插入表尾空间 并更新顺序表的有效长度
template<typename DataType>bool SeqList<DataType>::insertElement(DataType data)
{
	int currentIndex = length;//记录新元素的插入位置
	if (length>maxSize)//判断顺序表是否已满
		return false;
	else
	{
		elements[currentIndex]=data;
		length++;
		return true;
	}
}

插入函数返回的bool型变量用以反馈插入操作是否成功
顺序表的删除操作
删除操作要比插入操作麻烦 因为此处删除是指删除顺序表中指定元素
删除元素后 此元素后面的元素需移动弥补空缺

template<typename DataType>bool SeqList<DataType>::deleteElement(int location)
{
	if(location>=length||location<0)
		return false;
	else
	{
		for(int i=location;i<length;i++)
			elements[i]=element[i+1];
		length--;
		return true;
	}
}

获取指定位置的元素

template<typename DataType>DataType SeqList<DataType>::getElement(int location)
{
	if(location>=length||location<0)
		return 0;
	else
		return elements[location];
}

修改指定位置的元素

template<typename DataType>bool SeqList<DataType>::changeElement(int location,DataType newdata)
{
	if(location>=length||location<0)
		return false;
	else
	{
		elements[location]=newdata;
		return true;
	}
}

1.2.1 链表

与顺序表一样 链表的数据逻辑组织结构也是一维的 但链表的物理结构与顺序表不同 链表的物理存储结构是一堆地址任意的存储单元(不一定相邻)
链表的定义
将数据元素存放在不连续的地址空间中的一种线性表。
其分为单链表、双链表、循环链表几类。

1.2.2 单向链表

单链表的每个结点都有数据域和指针域,数据域用来存放结点的数据,指针域用来存放下一个结点的地址。
整个链表会有一个表头 来存放第一个结点的地址。
每个结点的后面一个节点称为该结点的后继。
链表也有一个表尾 其没有后继, 指向空指针(NULL)
单链表
从图中可以看出 单链表就具有如下特征:

  • 单链表结点的物理位置不一定连续,单链表逻辑上通过指针实现连续。
  • 单链表有一个头结点和一个尾结点 并且只有尾结点没有后继结点,其他结点有且只有一个后继结点
  • 只要知道了链表的头结点 就可以遍历整个链表
template<typename DataType>class LinkList
{
public:
    //
    LinkList()
    {
        head = new ListNode();
    }
    LinkList(ListNode *node)
    {
        head = node;
    }
    ~LinkList()
    {
        delete head;
    }
    bool insertNode(ListNode *q, DataType newData);//插入结点
    bool removeNode(LinkNode *q);//删除结点
    ListNode *findNode(DataType value);//查找指定值的结点并返回位置
    bool clearLink();//清空链表
    DataType getNodeData(ListNode *p);//获取结点数据
private:
    ListNode *head;//头结点
};
template<typename DataType>class ListNode
{
public:
    ListNode()
    {
        next = NULL;
    }
    ListNode(DataType item,ListNode<DataType>*nextNode=NULL)
    {
        data = item;
        next = nextNode;
    }
    ~ListNode()
    {
        next = NULL;
    }
    //获取结点内的数据
    DataType getData()
    {
        return data;
    }
    //获取指针域
    ListNode* getNext()
    {
        return next;
    }
private:
    friend typename LinkList<DataType>;
    //将LinkList设为友元类 方便对node的成员数据和方法的访问
    DataType *next;//指向下一个结点的指针
    DataType data;//结点中的数据
};

上面有两个类,一个是表示链表结点的ListNode类 一个是表示链表整体的LinkList类
在ListNode中将LinkList设为ListNode的友元类 便于链表类对结点的内部成员变量和成员方法的访问
一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接
链表的基本操作
单链表主要执行链表的插入结点、删除结点、查找指定值的结点和清空结点4种操作
插入结点
可以分为两种插入情况

  1. 在单链表的末尾添加结点
  2. 在第i个结点后添加新结点

在第i个结点后添加新结点:

  1. 创建一个待插入的结点 并分配给其内存空间, 并将指针域设为NULL
  2. 找到单链表的第i个结点位置,获取其地址,将待插入结点插入在结点i与其后继结点之间

结点插入

template<typename DataType>bool LinkList<DataType>::insertNode(int i,DataType newData)
{
    ListNode<DataType> *p = head;//设置游标指针,初始化头结点地址
    int j;
    for (j = 1; j <= i - 1;j++)//查找第i个结点 指针需要移动i-1次
    {
        p = p->next;
        if(p==NULL)//如果指针为空 则不存在该结点  或已到链表末尾
        {
            break;
        }
    }
    if(p==NULL&&j<(i-1))//指针为空且没有到第i个位置  说明不存在第i个结点
    {
        std::cout << "插入位置无效" << endl;
        return false;
    }
    ListNode<DataType> *node = new ListNode<DataType>(newData);//建立新结点node
    node->next = p->next;//将node的next指针复制为p的后继结点地址
    p->next = node;//p的后继指针指向node
    return true;
}

对于在单链表的末尾添加结点的操作,只需要我们遍历链表找到尾结点,然后将尾结点的后继指针指向新建结点就可以。
删除结点

template<typename DataType>bool LinkList<DataType>::removeNode(ListNode<DataType> *q)
{
    if(q==NULL)//判断待删除结点是否存在
    {
        std::cout << "待删除结点不存在" << endl;
        return false;
    }
    ListNode<DataType> *tempPointer = head;//设置游标指针 初始化为头结点
    while(tempPointer->next!=q)//遍历单链表  找到q所指向结点的前驱结点
    {
        tempPointer = tempPointer->next;
    }
    tempPointer->next = q->next;//将q结点的后继结点赋值给其前驱结点的next指针
    delete q;
    return true;
}

删除结点的关键是找到该结点在链表中的前驱节点
查找特定值的结点

template<typename DataType>ListNode<DataType>* LinkList<DataType>::findNode(DataType value)
{
    ListNode<DataType> *currentPointer = head;//设置游标指针
    //判定游标指针所指的结点是否与value相等
    while(currentPointer!=NULL&&currentPointer->data!=value)
    {
        currentPointer = currentPointer->next;
    }
    if(currenterPointer==NULL)//判断结点是否存在
    {
        std::cout << "没有找到该结点!程序异常退出!" << endl;
        exit(1);
    }
    return currentPointer;//返回找到的结点的指针
}

清空链表
清空操作的主要思想就是让头结点head的next指针域为空 回收各个结点的内存空间

template<typename DataType>void LinkList<DataType>::clearLink()
{
    ListNode<DataType> *current = head;//设置游标指针
    while(head->next!=NULL)//判断head的后继结点是否为NULL
    {
        current = head->next;//将current指向head的后继结点
        head->next = current->next;//将current的后继结点赋值给head的next域
        delete current;//回收current结点所占的空间;
    }
}

1.2.3 双向链表

区别于单向链表 双向链表的特点是每个结点包括两个指针域和一个数据域 两个指针域por和nxt分别指向该结点的前驱和后继元素。(除了 头结点没有前驱,尾结点没有后继元素)
双向链表
双向链表能更方便地寻找到结点地前驱,从任何一个结点都可以到达链表地头结点和尾结点 同样也可以进行插入结点、删除结点、清空、查找等操作。

#include<iostream>
template<typename DataType>class DoubleList;
template <typename DataType>class DoubleListNode//双链表结点定义
{
private:
    friend class DoubleList<DataType>;//声明链表类为友元类
    DoubleListNode():m_pprior(NULL),m_pnext(NULL){}//无参构造函数,将指针初始化为NULL
    DoubleListNode(const DataType item,DoubleListNode<DataType>*prior=NULL,DoubleListNode<DataType>*next=NULL):m_data(item),m_pprior(prior),m_next(next)//带参构造函数初始化数据域和指针域
    {}
    ~DoubleListNode()//析构函数
    {
        m_pprior = NULL;
        m_next = NULL;
    }
    DataType m_data;//数据域
    DoubleListNode *m_pprior;//指向前驱结点的指针域
    DoubleListNode *m_next;//指向后继结点的指针域
public:
    DataType getData()//获取结点内的数据值
    {
        return this->m_data;
    }
};
template <typename DataType>class DoubleList//双向链表定义
{
    public:
        DoubleList()
        {
            head = new DoubleListNode<DataType>();
        }
        ~DoubleList()
        {
            cleanDoubleLink();
            delete head;
        }
        void cleanDoubleLink;//清空双向链表
        int getlength();//获取链表长度
        DoubleListNode<DataType> *findNode(int i = 0);//寻找第i各结点
        DoubleListNode<DataType> *findData(DataType item);//寻找给定值数据的结点
        bool insertNode(DataType item, int i = 0);//在第i个结点后插入新结点
        bool removeNode(int i = 0);//删除第i个结点
        DataType getData(int i = 0);//获取第i个结点
    private:
        DoubleListNode<DataType> *head;//头指针
};

清空链表

template<typename DataType>void DoubleList<DataType>::cleanDoubleLink(){
    DoubleListNode<DataType> *pmove = head->m_pnext, *pdel;
    while(pmove!=NULL)//不断删除头结点后一个结点  直到删除尾结点
    {
        pdel = pmove;
        pmove = pmove->m_pnext;
        delete pdel;
    }
    head->m_pnext = NULL;
}

获取链表长度

template<typename DataType>int DoubleList<DataType>::getlength(){
    int count = 0;
    DoubleListNode<DataType> *pmove=head->m_pnext;
    while(pmove!=NULL)
    {
        pmove = pmove->m_pnext;
        count++;
    }
    retuen count;
}

获取第i个结点

template<typename DataType>DoubleListNode<DataType>*DoubleList<DataType>::findNode(int n=1){
    if(n<1){//判断位置是否有效
        cout << "非有效位置" << endl;
        return NULL;
    }
    DoubleListNode<DataType> *pmove = head->m_pnext;
    for (int i = 1; i < n;i++)//查找指定位置的结点
    {
        pmove = pmove->m_pnext;
        if(pmove==NULL){//判断结点是否存在
            cout << "不存在指定结点" << endl;
            return NULL;
        }
    }
    return pmove;
}

在第i个结点后插入新结点

template<typename DataType>bool DoubleList<DataType>::insertNode(DataType item,int n)
{
    int i;
    if(n<1){
        cout << "插入位置无效" << endl;
        return 0;
    }
    DoubleListNode<DataType> *newnode = new DoubleListNode<DataType>(item), *pmove = head;
    if(newnode==NULL)
    {
        cout << "内存分配失败,新结点无法创建" << endl;
        exit(1);
    }
    for (i = 1; i < n;i++)
    {
        pmove = pmove->m_pnext;
        if(pmove==NULL&&i<n-1)
        {
            cout << "超出链表长度,插入位置无效!" << endl;
            return 0;
        }
    }
    newnode->m_pnext = pmove->m_pnext;
    if(pmove->m_pnext!=NULL)
    {
        pmove->m_pnext->m_pprior = newnode;
    }
    newnode->m_pprior = pmove;
    pmove->m_pnext = newnode;
    return 1;
}

删除第i个结点

template <typename DataType>bool DoubleList<DataType>::removeNode(int n=1)
{
    if(n<1||n>getlength()){
        cout << "位置不合法" << endl;
        return false;
    }
    DoubleListNode<DataType> *pmove = head->m_pnext, *pdel;
    for (int i = 1; i < n;i++)
    {
        pmove = pmove->m_pnext;
        if(pmove==NULL)
        {
            cout << "超出了链表的范围" << endl;
            return false;
        }
    }
    pdel = pmove;
    pmove->m_pprior->m_pnext = pmove->m_pnext;
    pmove->m_pnext->m_pprior = pmove->m_pprior;
    delete pdel;
    return true;
}

获取第n个元素的数据

template <typename DataType>DataType DoubleList<DataType>::getData(int n = 1)
{
    if(n<1||n>getlength())
    {
        cout << "指定位置无效" << endl;
        exit(1);
    }
    DoubleListNode<DataType> *pmove = head->m_pnext;
    for (int i = 1; i < n;i++){
        pmove = pmove->m_pnext;
        if(pmove==NULL){
            cout << "指定结点不存在" << endl;
            exit(1);
        }
    }
    if(pmove==NULL)
    {
        cout << "指定结点不存在" << endl;
        return NULL;
    }
    return pmove->m_data;
}

获取指定值数据的结点

template <typename DataType>DoubleListNode<DataType>* DoubleList<DataType>::findData(DataType item)
{
    DoubleListNode<DataType> *pmove = head->m_pnext;
    if(pmove==NULL)
    {
        cout << "链表是空链表" << endl;
        exit(i);
    }
    while(pmove->m_data!=item)
    {
        pmove = pmove->m_pnext;
        if(pmove==NULL)
        {
            cout << "没有所查找的结点" << endl;
            exit(1);
        }
    }
    return pmove;
}

1.2.4 循环链表

这是一种特殊的链表形式 它的尾结点的指针域指向头结点构成了一个环形。从循环链表的头结点出发,遍历链表后可以再次回到头结点。可以利用循环链表实现链式的循环队列。
循环链表
本链表 读者可以自行尝试 私信发源码
链表的讨论暂告一段落

参考书籍《妙趣横生的算法(C++语言实现)》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值