数据结构-线性表

线性表

 

线性表是典型线性数据结构。

线性表是由n(n≥0)个数据元素组成的一个有限序列,线性表中数据元素的个数n称为线性表的长度。当n=0时,称为空表。

非空线性表满足线性结构的三个特性。

线性表的数据元素可以是由一个数据项组成的简单数据元素,也可以是多个数据项组成的复杂数据元素。

线性表的表示方式:

其中ei(i=1,2,3...n),e1为首结点,en为尾结点。线性表中一个数据元素称为一个结点。

线性表的操作:

 创建线性表;

 删除线性表;

 插入新元素;

 判断线性表是否为空;

 计算线性表长度;

 取某一个元素;

 按照关键字查找指定元素;

 删除第k个元素;

 删除元素根据关键字;

 输出线性表;

线性表的抽象数据类型定义:数据元素用T表示,List是线性表的抽象数据类型名

线性表的顺序存储:顺序表

存放线性表最简单的方法是顺序存储。

高级语言的一维数组和线性表的逻辑结构相似,通常用定义一个一维数组来实现线性表的顺序存储。

把线性表的结点按照逻辑顺序一次存放在一组地址连续的存储单元里就构成了线性表的顺序存储,采用顺序存储的线性表叫顺序表。

线性表的顺序存储结构的特点:

 线性表中所有元素所占的存储空间是连续的。

 线性表的逻辑顺序与物理顺序一致。

 数组中的每个元素的位置可以用公式来确定。

 

使用C++声明顺序表类模板

template<class T>
    class LinearList
    {
        private:
           int length;    //当前数组元素个数
           int MaxSize;  //线性表中最大元素个数
           T *element;       //一维动态数组
        public:
           LinearList(int LLMaxSize);//构造函数,创建空表
           ~LinearList();       //析构函数,删除表
           LinearList <T>&Insert(int k,constT& x);   //在第k个位置插入元素x,返回插入后的线性表
           bool IsEmpty()const; //判断表是否为空,表为空返回true,表非空返回false
           int GetLength() const;   //返回表中数据元素的个数
           bool GetData(int k,T& x);   //将表中第k个元素保存到x中,不存在则返回false
           bool ModifyData(int k,const T&x)//将表中第k个元素修改为x,不存在则返回false
           int Find(const T& x); //返回x在表中的位置,如果x不在表中返回0
           LinearList<T>& DeleteByIndex(int k,T& x); //删除表中第k个元素,并把它保存在x中,返回删除后的线性表
           LinearList<T>& DeleteByKey(const T& x,T& y)//删除表中关键字为x元素,返回删除后的线性表,删除的元素放入y中
           void OutPut(ostream& out) const;   //将线性表放到输出流out中输出
    }

顺序表的实现

顺序表插入算法实现:

 1、判断插入位置的合理性以及表是否已满

 2、从最后一个元素开始,将每个元素向后移动一个位置,直到第i个位置空闲

 3、在第i个位置放入新元素x

 4、将线性表长度加1

顺序表的删除算法实现:

 1、判断删除位置的合理性

 2、从第i+1个元素开始,依次向后直到最后一个元素,将每个元素向前移动一个位置

 3、将线性表长度减1

顺序表类操作的实现

//实现构造函数
template<class T>
LinearList<T>::LinearList(int LLMaxSize)
{
    MaxSize = LLMaxSize;
    element = new T[LLMaxSize];
    length = 0;
}
//实现析构函数
template<class T>
LinearList<T>::~LinearList()
{
    delete []element;
}
//实现插入新数据元素
template<class T>
LinearList<T>& LinearList<T>::Insert(int k,const T& x)
{
    if(k<1 || k>length+1)
        cout<<"元素数组下标越界,添加元素失败";
    else
        if(length==MaxSize)
            cout<<"此表已满,无法添加新元素";
       else{
            for(int i=lenght;i>k-1;i--)
                element[i] = element[i-1];//移动元素
            element[k-1]=x;                //插入元素
            length++;                  //表长+1
        }
    return *this;
}
//实现判断是否为空表
template<class T>   
bool LinearList<T>::IsEmpty() const{
    return length=0;       
}
//实现求当前表的长度
template<class Tint LinearList<T>::GetLength() const
{
    return length;
}
//实现按位置取元素
template<class T>
bool LinearList<T>::GetData(int k,T& X)
{
    if(k<1 || k>length)
        return false;
    else
    {
        x = element[k-1];
        return true;
    }
}
//实现按位置修改元素
template<class T>
bool LinearList<T>::ModifyData(int k,T& X)
{
    if(k<1 || k>length)
        return false;
    else
    {
        element[k-1] = x;
        return true;
    }
}
//实现按关键字查找
template<class T>
int LinearList<T>::Find(const T& x)
{
    for(int i=0;i<length;i++)
        if(element[i] == x)
            return i+1;
    return 0;
}
//实现按位置删除
template<class T>
LinearList<T>& LinearList<T>::DeleteByIndex(int k,T& x)
{
    if(GetData(k,x))
    {
        for(i=k-1;i<length-1;i++)
            element[i] = element[i+1];  //移动元素
        length--;                   //表长-1
    }
    else
        cout<<"元素下标越界,删除失败";
    return *this;
}
//实现按关键字删除
template<class T>
LinearList<T>& LinearList<T>::DeleteByKey(const T& x,T& y)
{
   int index =Find(x);
   if(index!=0)
       return DeleByIndex(index,y);
    else
    {
        cout<<"没有此元素,删除失败";
        return *this;
    }
}
//实现顺序表的输出
template<class T>
void LinearList<T>::OutPut(ostream& out) const
{
    for(int i=0;i<length;i++)
        out<<element[i]<<endl;
}
//重新载入运算符<<
template<class T>
ostream& operator<<(ostream& out,LinearList<T>& x)
{
    x.OutPut(out);
    return out;
}

结点类模板 头文件

/* Node.h:数据元素Node*/
#ifndef NODE
#define NODE
#include<iostream>
#include<cstring>
using namespace std;
class Node
{
public:
    Node(char* NumberOfStudent,char* NameOfStudent,int grade[]);
    Node(){};//无参构造函数
    Node& GetNode();//得到结点数据
    void OutPutNode(ostream& out) const; //输出结点数据
private:
    string StdNumber;
    string StdName;
    int Score[3];
   
};
//实现构造函数
Node::Node(char* NumberOfStudent,char* NameOfStudent,int grade[])
{
    StdNumber = NumberOfStudent;
    StdName = NameOfStudent;
    for(int i=0;i<3;i++)
        Score[i]=grade[i];
}
//实现得到结点数据函数
Node& Node::GetNode()
{
    return *this;
}
//实现输出结点数据函数
void Node::OutPutNode(ostream& out) const
{
    out<<StdNumber<<" "<<StdName<<endl;
    out<<"语文:"<<Score[0];
    out<<"数学:"<<Score[1];
    out<<"英语:"<<Score[2];
}
//重载插入运算符<<
ostream& operator<<(ostream& out,const Node& x)
{
    x.OutPut(out);
    return out;
}
#endif

复杂数据元素顺序表的应用问题

#include<iostream>
using namespace std;
#include "LinearList.h"
#include "Node.h"
int main()
{
    LinearList<Node> NodeLList(10)//最多有10个以Node对象为数据元素的顺序表
    int grade1[3] = {99,100,95};
    int grade2[3] = {95,98,88};
    int grade3[3] = {90,90,90};
    Node Node1("1010001","穆桂英",grade1);
    Node Node1("1010001","穆桂英",grade1);
    Node Node1("1010001","穆桂英",grade1);
    Node x;
    //将两个结点插入表中
    NodeLList.Insert(1,Node1);
    NodeLList.Insert(2,Node2);
    //显示当前表的状态
    cout<<"当前表的长度为:"<<NodeLList.GetLength()<<endl;
    cout<<"当前表的元素为:\n"<<NodeLList<<endl;
    //将表中第2个元素输出
    NodeLList.GetData(2,x);
    cout<<"表中第2个元素为:\n"<<x<<endl;
    //删除表中第2个元素,修改第1个元素的信息,显示当前表的状态
    NodeLList.DeleteByIndex(2,x);
    cout<<"刚刚删除的元素为:\n"<<x<<endl;
    NodeLList.ModifyData(1,Node3);
    cout<<"当前表的长度为:"<<NodeLList.GetLength()<<endl;
    cout<<"当前表的元素为:\n"<<NodeLList<<endl;
    return 0;
}

顺序表优缺点

优点:简单、存储密度大、空间利用率高、存储效率高。

缺点:顺序表中插入和删除操作时,需要移动大量的数据元素,浪费时间;

 实际应用中顺序表的长度不好估计,往往需要为顺序表分配足够大的内存空间,造成浪费。

总结:对于元素变动频繁、长度变化比较大的线性表,不宜采用顺序存储结构,适合采用链式存储结构。

线性表的链式存储结构:线性链表

线性链表是用一组任意的存储单元存储线性表中的数据元素。

线性链表中的结点可以是连续的,也可以是不连续的,可以零散分布在内存中任意位置中。

线性链表的数据元素的逻辑顺序和物理顺序不一定相同。

线性链表包括单向链表、循环链表和双向链表。

链式存储结构需要借助C++高级语言中的指针类型来实现。

链式存储结构:通过结点的指针域将n个结点按其逻辑结构连接在一起的数据存储结构,称为链式存储结构

链式存储结构中每一个数据元素对应一个存储单元,这种存储单元称为存储结点,简称结点。

每个结点分两部分:一部分是数据域,存放数据元素的值;一部分是指针域,存放指向与该结点在逻辑上相连的其他结点。

对于线性表,指针域用于指向该结点的前一个或者后一个结点。

单向链表

线性链表中,用一个专门的指针指向线性表第一个结点,每一个结点的指针都指向它的下一个结点,最后一个结点的指针为空(null或0),表示链表终止,这样的的链表称为单向链表。

链式结构,在第一个结点前面加一个头结点程序会变得简洁,程序运行速度会提高。所以链式存储结构一般都采用带有头结点的结构。

  • 为什么加头结点会简洁会提高运行速度?

单线链表结点类模板LinkNode

//存储结点类
template<class T>
class LinkNode  //结点类
{
    template<class T>
    friend class LinkList;      //将链式表类声明为友类
public:
    LinkNode()    //构造函数
    {
        next = NULL;
    }
private:
    T data;              //结点元素
    LinkNode<T> *next;   //指向下一个结点的指针
}

单向链表类模板LinkList

//单向链表类
template<class T>
class LinkList  //结点类
{
public:
    LinkList();      //构造函数
    ~LinkList();     //构造函数
    LinkList<T>& Insert(int k,const T& x);
    bool IsEmpty()const; //判断表是否为空,表为空返回true,表非空返回false
    int GetLength() const//返回表中数据元素的个数
    bool GetData(int k,T& x);   //将表中第k个元素保存到x中,不存在则返回false
    bool ModifyData(int k,const T&x)//将表中第k个元素修改为x,不存在则返回false
    int Find(const T& x);   //返回x在表中的位置,如果x不在表中返回0
    LinkList<T>& DeleteByIndex(int k,T& x);   //删除表中第k个元素,并把它保存在x中,返回删除后的线性表
    LinkList<T>& DeleteByKey(const T& x,T& y);   //删除表中关键字为x元素,返回删除后的线性表,删除的元素放入y中
    void OutPut(ostream& out) const;   //将线性表放到输出流out中输出
private:
    LinkNode<T> *head;   //指向链表的第一个头结点的指针
}

单向链表插入算法

 单向链表中,表中已经有n个元素,往第k个位置处插入结点NewNode时:

 ①当k>n+1或者k<1时,插入位置不正确,报错;

 ②当时空表且k=1,只需要将head->next指向新节点即可;

 ③当不是空表且k=1,需要将新节点的next指针指向第一个元素结点,再将头结点的指针域指向newNode结点;

 ④当1<k<=n,将newNode的next指针指向第k个元素结点,将第k-1个元素结点的next指针指向newNode结点。

//假设要在第k个位置上插入元素值为e的NewNode结点,p是一个结点指针
p=head;
for(int i=1;i<k;i++)
    p=p->next;        //将p指针移动到第k-1个结点
NewNode->next=p->next; 
//将新节点newNode插入到链表中
p->next=newNode;

单向链表删除操作

单向链表中,表中已经有n个元素,要删除第k个元素:

 ①当k>n+1或者k<1时,由于没有第n+1或第0个元素,报错;

 ②当k=1时,将头结点的指针域指向第二个结点,然后释放第k个结点;

 ③单1<k<n时,将第k-1个元素结点的指针域指向第k+1个元素结点,然后释放第k个结点;

 ④当k=n时,将第n-1个结点的next指针赋值为NULL,然后释放第k个结点。

//假设要删除位置为k的元素,p和q是两个结点指针
p=head;
for(int i=1;i<k;i++)
    p=p->next;        //将p指针移动到第k-1个结点
q=p->next;           //q指向待删除的第k个结点
p->next = q->next;       //将第k个结点从链表中逻辑删除
delete q;            //物理删除该结点

实现插入新数据元素

template<class T>
LinkList<T>& LinkList<T>::Insert(int k,const T& x)
{
    LinkList<T> *p = head;             //p指向头结点
    LinkList<T> *newNode= new LinkList<T>; //创建待插入的新节点
    newNode->data=x;
    int len=GetLength();
    if(k<1||k>len+1)     //插入新元素的位置错误
        cout<<"元素下标越界,添加新元素失败";
    else
    {
       for(int i=1;i<k;i++)
           p=p->next;        //将p指针移动到第k-1个结点
       NewNode->next=p->next;  //将新节点newNode插入到链表中
       p->next=newNode;  
    }
    return *this;
}

实现按位置删除

template<class T>
LinkList<T>& LinkList<T>::DeleteByIndex(int k,T& x)
{
    if(GetData(k,x))
    {
        LinkList<T> *p = head;             //p指向头结点
        LinkList<T> *q = NULL;             //指向空地址
        for(int i=1;i<k;i++)
            p=p->next;       //将p指针移动到第k-1个结点
        q=p->next;           //q指向待删除的第k个结点
       p->next = q->next;       //将第k个结点从链表中逻辑删除
       delete q;            //物理删除该结点
    }
    else
        cout<<"元素下标越界,删除失败";
   
    return *this;
}

实现按关键字删除

template<class T>
LinkList<T>& LinkList<T>::DeleteByIndex(const T& x,T& y)
{
    int index=Find(x);             //得到要删除的元素位置
    if(index!=0)
        return DeleteByIndex(index,y);
    else
    {
        cout<<"没有此元素,删除失败";
        return *this;
    }
}

单向链表基本操作的实现

//构造函数的实现(创建空单向链表)
template<class T>
LinkList<T>::LinkList()
{
    head = new LinkNode<T>();//创建头结点
}
//析构函数的实现(清空单向链表)
template<class T>
LinkList<T>::LinkList()
{
    T x;
    int len=GetLength();
    for(int i=len;i>=1;i--)
        DeleteByIndex(i,x);     //释放所有结点
    delete head;            //释放头结点
}
//判断是否为空表
template<class T>
bool LinkList::IsEmpty() const
{
    return head->next = NULL;
}
//求当前表的长度
template<class T>
int LinkList<T>::GetLength() const
{
    int length = 0;
    LinkList<T> *p = head->next;
    while(p)
    {
        length++;
        p=p->next;
    }
    return length;
}
//按位置取元素
template<class T>
bool LinkList<T>::GetData(int k,T& x)
{
    LinkList<T> *p = head->next;
    int index=1;
    if(k<1||k>GetLength())
        return false;
    while(p!=NULL && index<k)
    {
        index++;
        p=p->next;
    }
    if(p==NULL)
        return false;
    else
    {
        x=p->data;
        return true;
    }
}
//按位置修改元素
template<class T>
bool LinkList<T>::ModifyData(int k,const T& x)
{
    LinkList<T> *p = head->next;
    int index=1;
    if(k<1||k>GetLength())
        return false;
    while(p!=NULL && index<k)
    {
        index++;
        p=p->next;
    }
    if(p==NULL)
        return false;
    else
    {
        p->data=x;
        return true;
    }
}
//按关键字查找
template<class T>
int LinkList<T>::Find(const T& x)
{
    LinkList<T> *p = head->next;
    int index=1;
    while(p!=NULL && p->data!=x)
    {
        p=p->next;
        index++;
    }
    if(p!=NULL)
        return index;
    else
        return 0;
}
//实现单链表的输出
template<class T>
void LinkList<T>::OutPut(ostream& out)
{
    LinkList<T>* p = head->next;
    while(p!=NULL)
    {
        out<<p->data<<endl;
        p=p->next;
    }
}
//重载插入运算符<<
template<class T>
ostream& operator<<(ostream& out,const LinkList<T>& x)
{
    x.OutPut(out);
    return out;
}

循环链表

将单向链表最后一个结点的指针指向头结点,这样整个链表就构成一个循环,这种链式存储结构称为单向循环链表,简称循环链表。

只有头结点的循环链表称为空循环链表。

循环链表中至少有一个结点存在,使空表与非空表的运算一致。

和单向链表的区别:循环链表空表的条件:head->next==head; 单向链表空表的条件:head->next==NULL。

双向链表

双向链表每个结点包含两个指针域,一个指针指向其前驱结点(prev指正),一个指针指向其后继结点(next指针)。

将双向链表的的第一个结点的prev指针指向最后一个结点,将最后一个结点的next指针指向第一个结点,就构成了双向循环链表。

posted @   极厌  阅读(450)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
· 零经验选手,Compose 一天开发一款小游戏!
点击右上角即可分享
微信分享提示