数据结构与算法:顺序表和链表

news2024/9/28 11:12:47

目录

一、线性表

二、顺序表

 三、链表


一、线性表

        线性表( linear list )是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...

        线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

        其中数组(某种程度上可以认为是顺序表)在物理空间上是连续存储的,而链表是由不同的小块通过某些“记忆”顺次连接存储的,这些小块或连续,或不连续,但是在逻辑上都呈线性。

二、顺序表

         顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

顺序表一般又可以分为静态顺序表和动态数据表:

        静态数据表是用一段长度固定的物理空间来对数据进行存储,数组长度是固定的。堆开辟的数组在特殊情景下,使用比较多,尤其是动态顺序表。

// 静态顺序表:通过宏来替换定义一个数组
#define N 10
struct Arr
{
    public:
        int n;
        int arr[N];
};
// 动态顺序表:在数组长度不确定的时候,采用堆空间来创建顺序表
class Arr
{
    public:
        Arr(int _n):n(_n),arr(new int[n]){}
    protected:
        int n;
        int* arr;
};

        显然,通过宏替换或者是栈上直接开辟的数组对于长度是已经锁死了的,当数据足够庞大的时候,N为10不够,这时需要修改为比较大的值。如果是本来就给N定为比较大的数,在面对较少的数据时就会显得浪费空间。所以在实践中,我们更推荐使用的是动态数组。动态数组可以实现在数组长度达到最大时扩容继续添加数据的作用。
        下面是一串采用C++实现的顺序表的代码:

(1)SeqList.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cassert>
using namespace std;
class SeqList
{
public:
	//初始化
	SeqList();
	//销毁
	~SeqList();
	//头插
	void PushFront(int data);
	//头删
	void PopFront();
	//尾插
	void PushBack(int data);
	//尾删
	void PopBack();
	//打印
	void Print();
	//寻找
	int* Find(int data);
	//插入
	void Insert(int pos, int data);
	//清除
	void Erase(int data);
	//检查容量
	void Check();
private:
	int* ptr;
	int index;
	int capacity;
};

(2)SeqList.cpp

#include "SeqList.h"
SeqList::SeqList()
{
	ptr = nullptr;
	index = 0;
	capacity = 0;
}
SeqList::~SeqList()
{
	if (ptr!=nullptr)
	{
		delete[]ptr;
		ptr = nullptr;
		capacity = 0;
		index = 0;
		cout << "~SeqList Destroyed" << endl;
	}
}
void SeqList::PushFront(int data)
{
	Check();
	for (int i = index - 1; i >= 0; i--)
	{
		ptr[i + 1] = ptr[i];
	}
	ptr[0] = data;
	index++;
}
void SeqList::PopFront()
{
	for (int i = 1; i < index; i++)
	{
		ptr[i - 1] = ptr[i];
	}
	index--;
}
void SeqList::PushBack(int data)
{
	Check();
	assert(ptr);
	ptr[index++] = data;
}
void SeqList::PopBack()
{
	assert(index >= 0);
	index--;
}
void SeqList::Print()
{
	for (int i = 0; i < index; i++)
	{
		cout << ptr[i] << " ";
	}
	cout << endl;
}
int* SeqList::Find(int data)
{
	for (int i = 0; i < index; i++)
	{
		if (ptr[i] == data)
		{
			return ptr + i;
		}
	}
	return nullptr;
}
void SeqList::Insert(int pos, int data)
{
	assert(pos > 0);
	Check();
	for (int i = index - 1; i >= pos - 1; i--)
	{
		ptr[i + 1] = ptr[i];
	}
	ptr[pos-1] = data;
	index++;
}
void SeqList::Erase(int data)
{
	int flag = 1;
	for (int i = 0; i < index; i++)
	{
		if (ptr[i] == data)
		{
			for (int j = i; j <index; j++)
			{
				ptr[j] = ptr[j + 1];
			}
			index--;
			flag = 0;
		}
	}
	if (flag)
	{
		cout << "No Data" << endl;
	}
}
void SeqList::Check()
{
	if (ptr == nullptr || index == capacity)
	{
		int new_capcaity = (ptr == nullptr ? 4 : capacity * 2);
		if (capacity != new_capcaity && capacity != 0)
		{
			int* temp = new int[new_capcaity];
			for (int i = 0; i < index; i++)
			{
				temp[i] = ptr[i];
			}
			delete[] ptr;
			ptr = temp;
			assert(ptr);
			capacity = new_capcaity;
			return;
		}
		ptr = new int[new_capcaity];
		capacity = new_capcaity;
		assert(ptr);
	}
}

        值得注意的是,如果使用模板template不能将函数定义与声明分离放在两个文件中,否则会导致很多的麻烦。

        顺序表在实现上是比较简单的,但是在某些方面上效率是比较低的。比如在进行增加数据的操作时,如果数组已经满了,那么需要开辟新空间,并且将原先的数据挪到新空间,并释放旧空间;然后在进行扩容的操作时,一般是扩2倍,导致资源利用率会随着数据的减少而减少;在进行头插和中间位置的插入时,时间复杂度为O(N)。

 三、链表

        链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

1.单向不循环链表

// 不带哨兵位的单向链表
#include <iostream>
#include <cassert>
using namespace std;
template<class T>
struct Node
{
    Node<T> *next;
    T data;
    Node(T _data) : data(_data),next(nullptr){}
};
template<class T>
class SLTList
{
    protected:
    Node<T> *pHead;
    public:
    SLTList():pHead(nullptr){}
    void push_back(T data)
    {
        // 头为空就直接改变头指向
        if(!pHead)
            pHead = new Node<T>(data);
        // 头不为空就尾插
        else
        {
            Node<T> *tail = pHead;
            while(tail->next)
            {
                tail = tail->next;
            }
            tail->next = new Node<T>(data);
        }
    }
    void pop_back()
    {
        // 头为空就直接返回
        if(!pHead)
            return;
        // 只有一个头结点,就释放该节点的权限并置空
        if(!pHead->next)
        {
            delete pHead;
            pHead = nullptr;
            return;
        }
        Node<T> *fast = pHead;
        Node<T> *slow = pHead;
        while(fast->next)
        {
            slow = fast;
            fast = fast->next;
        }
        slow->next = nullptr;
        delete fast;
        fast = nullptr;
    }
    void push_front(T data)
    {
        // 头插 更改头指针的指向
        Node<T> *newNode = new Node<T>(data);
        newNode->next = pHead;
        pHead = newNode;
    }
    void pop_front()
    {
        // 如果头为空,就直接返回
        if(!pHead)
            return;
        // 只有一个头结点,那就删除头结点,并把pHead置为空
        if(!pHead->next)
        {
            delete pHead;
            pHead = nullptr;
            return;
        }
        // 头非空,就创建一个新头指针,释放旧头指针,最后改变指向
        Node<T> *newHead = pHead->next;
        delete pHead;
        pHead = newHead;
    }
    void print()
    {
        // 打印输出链表的元素
        Node<T> *tail = pHead;
        while(tail)
        {
            cout << tail->data << endl;
            tail = tail->next;
        }
    }
    Node<T>* find(T data)
    {
        Node<T> *tail = pHead;
        while(tail)
        {
            if(tail->data==data)
                return tail;
            tail = tail->next;
        }
        return nullptr;
    }
    void insert(Node<T>* pos,T data)
    {
        if(!pos&&pos!=pHead)
            return;
        if(pos==pHead)
        {
            push_front(data);
        }
        else
        {
            Node<T> *prePos = pHead;
            while(prePos->next!=pos)
            {
                prePos = prePos->next;
            }
            Node<T> *newNode = new Node<T>(data);
            prePos->next = newNode;
            newNode->next = pos;
        }
    }
    void erase(Node<T>* pos)
    {
        if(!pos)
            return;
        else
        {
            if(pos==pHead)
            {
                pop_front();
                return;
            }
            Node<T> *prePos = pHead;
            while(prePos->next!=pos)
            {
                prePos = prePos->next;
            }
            prePos->next = pos->next;
            delete pos;
        }
    }

};
// 带哨兵位的单向链表
#include <iostream>
#include <cassert>
using namespace std;
// 带有哨兵位的单向链表
template <class T>
struct Node
{
    T data;
    Node<T> *next;
    Node(T _data = 0) : data(_data), next(nullptr) {}
};
template <class T>
class SLTList
{
protected:
    Node<T> *pHead;

public:
    SLTList() : pHead(new Node<T>) {}
    ~SLTList()
    {
        Node<T> *tail = pHead;
        while (tail->next)
        {
            Node<T> *tmp = tail;
            tail = tail->next;
            delete tmp;
            tmp = nullptr;
        }
    }
    // 尾插
    void push_back(T _data)
    {
        assert(pHead);
        Node<T> *tail = pHead;
        while (tail->next != nullptr)
        {
            tail = tail->next;
        }
        tail->next = new Node<T>(_data);
    }
    // 尾删
    void pop_back()
    {

        Node<T> *fast = pHead;
        Node<T> *slow = pHead;
        while (fast->next != nullptr)
        {
            slow = fast;
            fast = fast->next;
        }
        // 不能删除哨兵位
        if (fast != pHead)
            delete fast;
        slow->next = nullptr;
    }
    // 头插
    void push_front(T _data)
    {
        if (pHead)
        {
            Node<T> *oldTail = pHead->next;
            pHead->next = new Node<T>(_data);
            pHead->next->next = oldTail;
        }
    }
    // 头删
    void pop_front()
    {
        if (pHead && pHead->next)
        {
            Node<T> *tmp = pHead->next;
            pHead->next = pHead->next->next;
            delete tmp;
        }
    }
    // 寻找_data的元素位置
    Node<T> *find(T _data)
    {
        assert(pHead);
        Node<T> *pos = pHead->next;
        while (pos)
        {
            if (pos->data == _data)
                return pos;
            pos = pos->next;
        }
        return nullptr;
    }
    // pos前面插入
    void insert(Node<T> *pos, T _data)
    {
        assert(pHead);
        Node<T> *prePos = pHead;
        while (prePos->next != pos)
        {
            prePos = prePos->next;
        }
        prePos->next = new Node<T>(_data);
        prePos->next->next = pos;
    }
    // 删除某个位置的元素
    void erase(Node<T> *pos)
    {
        assert(pHead);
        if (pos)
        {
            Node<T> *prePos = pHead;
            while (prePos->next != pos)
            {
                prePos = prePos->next;
            }
            prePos->next = pos->next;
            delete pos;
            pos = nullptr;
        }
    }
    void print()
    {
        assert(pHead);
        Node<T> *tail = pHead->next;
        while (tail)
        {
            cout << tail->data << endl;
            tail = tail->next;
        }
    }
};

2.带头双向循环链表

#include <iostream>
#include <cassert>
using namespace std;
struct ListNode
{
    ListNode *prev;
    ListNode *next;
    int data;
};
ListNode *ListCreate(int data)
{
    ListNode *newNode = new ListNode;
    if (newNode)
    {
        newNode->data = data;
        newNode->prev = nullptr;
        newNode->next = nullptr;
        return newNode;
    }
    return nullptr;
}
void ListPrint(ListNode *pHead)
{
    assert(pHead);
    ListNode *tmp = pHead->next;
    cout << "哨兵位<=>";
    while (tmp != nullptr && tmp != pHead)
    {
        cout << tmp->data << "<=>";
        tmp = tmp->next;
    }
    cout << endl;
}
void ListPushBack(ListNode *pHead, int data)
{
    assert(pHead);
    ListNode *tmp = ListCreate(data);
    if(pHead->next!=nullptr)
    {
        ListNode *tail = pHead->prev;
        tail->next = tmp;
        tmp->prev = tail;
        tmp->next = pHead;
        pHead->prev = tmp;
    }
    else
    {
        pHead->next = tmp;
        pHead->prev = tmp;
        tmp->next = pHead;
        tmp->prev = pHead;
    }
}
void ListPopBack(ListNode*pHead)
{
    assert(pHead);
    if(pHead->next==nullptr||pHead->next==pHead->prev&&pHead->next==pHead)
    {
        cout << "Doubly linked list is empty!" << endl;
        return;
    }
    ListNode* oldTail = pHead->prev;
    ListNode *newTail = oldTail->prev;
    newTail->next = pHead;
    pHead->prev = newTail;
    delete oldTail;
    oldTail = nullptr;
}
void ListPushFront(ListNode*pHead,int data)
{
    assert(pHead);
    if(pHead->next)
    {
        ListNode *newHead = ListCreate(data);
        ListNode *oldHead = pHead->next;
        newHead->next = oldHead;
        newHead->prev = pHead;
        oldHead->prev = newHead;
        pHead->next = newHead;
    }
    else
    {
        ListNode *newHead = ListCreate(data);
        pHead->next = newHead;
        pHead->prev = newHead;
        newHead->next = pHead;
        newHead->prev = pHead;
    }
}
void ListPopFront(ListNode*pHead)
{
    assert(pHead);
    if(pHead->next==nullptr||pHead->next==pHead->prev&&pHead->next==pHead)
    {
        cout << "Doubly linked list is empty!" << endl;
        return;
    }
    ListNode *oldHead = pHead->next;
    ListNode *newHead = oldHead->next;
    pHead->next=newHead;
    newHead->prev = pHead;
    delete oldHead;
    oldHead = nullptr;
}
ListNode* ListFind(ListNode* pHead, int x)
{
    assert(pHead);
    ListNode *tail = pHead->next;
    while(tail!=pHead)
    {
        if(tail->data==x)
        {
            return tail;
        }
        tail = tail->next;
    }
    return nullptr;
}
void ListInsert(ListNode* pos, int x)
{
    ListNode *newNode = ListCreate(x);
    ListNode *prevNode = pos->prev;
    prevNode->next = newNode;
    newNode->prev = prevNode;
    newNode->next = pos;
    pos->prev = newNode;
}
void ListErase(ListNode*pos)
{
    ListNode *prevNode = pos->prev;
    ListNode *nextNode = pos->next;
    prevNode->next = nextNode;
    nextNode->prev = prevNode;
    delete pos;
    pos = nullptr;
}

        对应STL里面的List: http://t.csdnimg.cn/xFva4 。

        单链表的实现相对容易一点,但是在实现尾插和尾删的效率上是比较低的。而双向带头循环链表虽然实现起来相对复杂,但是在使用上却展示出了许多的优势。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1909288.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IC认证介绍

IC认证是什么&#xff1f; IC是加拿大工业部Industry Canada的简称&#xff0c;作为政府机构&#xff0c;负责电子电器产品进入加拿大市场的认证事务。与美国的FCC相似&#xff0c;IC目前只在电磁干扰上做限制。一般规定&#xff1a;仅限制EMI&#xff0c;认证方式也与FCC相同…

idea http client插件上传文件,并忽略https证书验证

上传文件 ### 传临时素材 图片 POST https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token{{access_token}}&typeimage Content-Type: multipart/form-data; boundary----WebKitFormBoundarywKUX3Xj6aL5Wssnb------WebKitFormBoundarywKUX3Xj6aL5Wssnb Conten…

星间链路与星地链路

目录 一、星间链路 1.1 层内星间链路&#xff08;Intra-layer ISLs&#xff09; 1.2 层间星间链路&#xff08;Inter-layer ISLs&#xff09; 1.3 实现方式 1.3.1 微波链路 1.3.2 激光链路 二、星地链路 2.1 星地链路的关键特性 2.1.2 Ka信关站 2.1.2 Q/V信关站 2.1…

Windows 控制中心在哪里打开,七种方法教会你

在 Windows 操作系统中&#xff0c;控制中心的概念可能稍有些混淆&#xff0c;因为 Windows 通常使用“控制面板”这一术语来指代用于配置系统设置和更改硬件及软件设置的中心区域。 不过&#xff0c;随着 Windows 的更新&#xff0c;微软也在逐步将一些设置迁移到“设置”应用…

前端面试题30(闭包和作用域链的关系)

闭包和作用域链在JavaScript中是紧密相关的两个概念&#xff0c;理解它们之间的关系对于深入掌握JavaScript的执行机制至关重要。 作用域链 作用域链是一个链接列表&#xff0c;它包含了当前执行上下文的所有父级执行上下文的变量对象。每当函数被调用时&#xff0c;JavaScri…

科普文:分布式系统的架构设计模式

一、分布式架构基本概念 分布式架构是一种计算机系统设计方法&#xff0c;它将一个复杂的系统划分为多个自治的组件或节点&#xff0c;并通过网络进行通信和协作。每个组件或节点在功能上可以相互独立&#xff0c;但又能够通过消息传递或共享数据来实现协同工作。分布式架构主要…

3.flink架构

目录 概述 概述 Flink是一个分布式的带有状态管理的计算框架&#xff0c;为了执行流应用程序&#xff0c;可以和 Hadoop YARN 、K8s 进行整合&#xff0c;当然也可以是一个 standalone 。 官方地址&#xff1a;速递 k8s 是未来的一种趋势&#xff0c;对资源管控能力强。

《金山 WPS AI 2.0:重塑办公未来的智能引擎》

AITOP100平台获悉&#xff0c;在 2024 世界人工智能大会这一科技盛宴上&#xff0c;金山办公以其前瞻性的视野和创新的技术&#xff0c;正式发布了 WPS AI 2.0&#xff0c;犹如一颗璀璨的星辰&#xff0c;照亮了智能办公的新征程&#xff0c;同时首次公开的金山政务办公模型 1.…

延时函数是怎么来的?频率和滴答计数之间的计算?(无ucos,小白向)

延时函数是怎么来的&#xff1f;频率之间的计算&#xff1f;&#xff08;无ucos&#xff0c;小白向&#xff09; 文章目录 延时函数是怎么来的&#xff1f;频率之间的计算&#xff1f;&#xff08;无ucos&#xff0c;小白向&#xff09;Systick定时器4个Systick寄存器1、CTRL -…

SSE打扮你的AI应用,让它美美哒

❝ 我从不幻想成功。我只会为了成功努力实践 大家好&#xff0c;我是柒八九。一个专注于前端开发技术/Rust及AI应用知识分享的Coder ❝ 此篇文章所涉及到的技术有 SSE Node( Express) EventSource React Tailwindcss 打字效果 因为&#xff0c;行文字数所限&#xff0c;有些概念…

【React】Google 账号之个性化一键登录按钮功能

“使用 Google 帐号登录”功能可快速管理网站上的用户身份验证。用户登录 Google 账号、表示同意&#xff0c;并安全地与平台共享其个人基础资料信息。 官方文档&#xff1a;链接 一、获取 Google API 客户端 ID 打开 Google API 控制台 中的凭据页面 创建或选择 Google API 项…

小米采取措施禁止国行版设备安装国际版系统 刷机后将报错无法进入系统

据知名官改版系统 Xiaomi.EU 测试者 Kacper Skrzypek 发布的消息&#xff0c;小米目前已经在开机引导中新增区域检测机制&#xff0c;该机制将识别硬件所属的市场版本&#xff0c;例如中国大陆市场销售的小米即将在安装国际版系统后将无法正常启动。 测试显示该检测机制是在开…

有浏览器就行,手把手带你从零微调大模型!

有浏览器就行&#xff0c;手把手带你从零微调大模型&#xff01; 今天分享一篇技术文章&#xff0c;你可能听说过很多大模型的知识&#xff0c;但却从未亲自使用或微调过大模型。 今天这篇文章&#xff0c;就手把手带你从零微调一个大模型。 大模型微调本身是一件非常复杂且…

自动化测试全攻略:从入门到精通!

1、自动化测试专栏 随着技术的发展和工作需求的增长&#xff0c;自动化测试已成为软件质量保障体系中不可或缺的一环。 为了帮助广大测试工程师、开发者和对自动化测试感兴趣的读者们更好地掌握这一技能&#xff0c;今年特别推出了全新的《自动化测试全攻略&#xff1a;从入门…

[Python爬虫] 抓取京东商品数据||京东商品API接口采集

本文结构&#xff1a; 一、引言 二、代码分享 三、问题总结 引言 这两天因为一些需求&#xff0c;研究了一下如何爬取京东商品数据。最开始还是常规地使用selenium库进行商品页的商品抓取&#xff0c;后来因为想要获取优惠信息&#xff0c;只能进入到商品详情页进行抓取&#x…

阶段三:项目开发---民航功能模块实现:任务24:航空实时监控

任务描述 内 容&#xff1a;地图展示、飞机飞行轨迹、扇区控制。航空实时监控&#xff0c;是飞机每秒发送坐标&#xff0c;经过终端转换实时发送给塔台&#xff0c;为了飞机位置的精准度&#xff0c;传输位置的密度很大&#xff0c;在地图位置显示不明显。本次为了案例展示效…

如何成功的设计BGA?

目前&#xff0c;用于容纳各种先进多功能半导体器件&#xff08;如 FPGA 和微处理器&#xff09;的标准封装是球栅阵列 &#xff08;BGA&#xff09;。BGA 封装中的组件用于各种嵌入式设计中&#xff0c;既可用作主机处理器&#xff0c;也可用作存储器等外设。多年来&#xff0…

抖音机构号授权源码全解析

抖音机构号授权源码是抖音平台为了方便机构用户进行授权管理而推出的一项服务。随着抖音平台的快速发展&#xff0c;越来越多的机构开始意识到抖音作为一种强大的营销渠道的价值。为了更好地利用抖音平台的资源&#xff0c;许多机构开始了解抖音机构号的功能和优势&#xff0c;…

css 文件重复类样式删除

上传文件 进行无关 className 删除 <div style"display: flex;"><input type"file" change"handleFileUpload" /><el-button click"removeStyles" :disabled"!fileContent">Remove Styles and Download&…

科普文:Linux服务器常用命令和脚本

Linux服务器常用的命令&#xff1a;find、grep、xargs、sort、uniq、tr、cut、paste、wc、sed、awk&#xff1b;提供的例子和参数都是最常用和最为实用的。 1.find 文件查找 查找txt和pdf文件 find . \( -name "*.txt" -o -name "*.pdf" \) -print 正…