数据结构--线性表

线性表

线性表是一种基础的数据结构,用于表示元素集合,这些元素按照一对一的线性关系排列。在线性表中,除了第一个和最后一个元素,每个元素都只有一个直接前驱和一个直接后继。

线性表可以分为两类:顺序表(线性表的顺序表示)和链表(线性表的链式表示)

 1.1、线性表的顺序存储表示 

//顺序存储的存储结构
#define MAXSIZE 100  //线性表的可能达到的最大长度
typedef struct
{
  ElemType *elem;  //存储空间的基地址
  int length;      //当前长度
 
}Sqlist;           //顺序表的结构体类型为Sqlist

typedef关键字用于为已存在的数据类型创建一个新的名字,如上面的代码中为新建的结构体起名为Sqlist。

Elemtype 是为了描述统一而制定的,实际运用时使用typedef 数据类型 Elemtype为Elemtype确定数据类型。(后面的status也是如此)

typedef 数据类型 Elemtype //eg.typedef int Elemtype

顺序存储中结构体也可以作为元素对象,即elem。举个例子:

//定义一个结构体,包含学生姓名和学号
typedef struct
{
  string name;   //姓名
  string ID;     //学号
}Student;

//定义顺序表
typedef struct
{
  Student *elem;
  int length;
}Sqlist;

说白了就是创建一个名为elem的动态数组,其中elem[i]中存储的都是学生这个结构体,其中length表明顺序表中的元素个数,最后一个元素为elem[length-1].

1.2、顺序表中的基本操作

(1)初始化

define OK 1
define OVERFLOW -2
Status InitList(Sqlist &L)
{//构造一个空的顺序表
  L.elem=new ElemType[MAXSIZE]; //为顺序表分配一个大小为MAXSIZE的空间
  if(!L.elem)exit(OVERFLOW);  
  L.length=0;
  return OK;
  
} 

这里有个代码小技巧,退出程序使用exit(OVERFLOW)而不直接使用exit(-2),而OVERFLOW也是我们进行预定义的 这是为什么呢.

虽然两种写法都可以,且都不会影响代码的运行结果,但是使用OVERFLOW就很容易知道是溢出错误。

此外,这里使用到了new关键字动态分配内存,即在程序运行时(而非编译时)分配的内存,它允许程序在需要时请求更多的内存,并在不再需要时释放内存。

new关键字的使用通常遵循以下步骤:

  1. 分配内存:new会在堆(heap)上为对象分配足够的内存。
  2. 构造对象:如果分配的是类类型的对象,new会调用该类的构造函数来初始化对象。
  3. 返回指针:new操作完成后,会返回一个指向分配内存的指针。

(2)取值

Status GetElem(Sqlist L,int i,Elemtype &e)
{
  if(i<1||i>L.length) return ERROR
  e=L.elem[i-1]
  return OK
}

(3)查找

int LcateElem(Sqlist L,Elemtype e)
{
  for(int i=0;i<L.length;i++)
     if(L.elem[i]==e)
        return i+1;
  return 0;
}

(4)插入

Status ListInsert(Sqlist &L,int i,Elemtype e) //i:插入位置 e:插入的元素
{
  if(L.length==MAXSIZE) return ERROR;
  if(i<1||i>L.length+1) return ERROR;
  for(int j=L.length-1; j>=i-1;j--)
     L.elem[j+1]=L.elem[j];
  L.elem[i-1]=e;
  ++L.length;
  return OK;

}

(5)删除(删除第i个元素)

Status ListDelete(Sqlist &L,int i)
{
  if(i<1||i>L.length) return ERROR;
  for(int j=i-1;j<L.length;j++)
     L.elem[j-1]=L.elem[j];
  --L.length;
  return OK;
}

2.1、线性表的链式表示

链表是一种线性数据结构,由一系列节点组成,每个节点包含数据和一个或多个指向其他节点的引用(或指针)。链表的特点是节点在内存中不必连续存储,它们通过指针连接,这样可以更有效地利用内存空间,并且可以快速地进行插入和删除操作。

线性表的线性表示可以称之为链表,链表又分为单链表、循环链表、双向链表。

  1. 单向链表(Single Linked List):每个节点只包含数据和指向下一个节点的指针。最后一个节点的指针指向null,表示链表的末尾。

  2. 双向链表(Double Linked List):每个节点除了数据和指向下一个节点的指针外,还有一个指向前一个节点的指针。这使得双向链表可以从两个方向遍历。

  3. 循环链表(Circular Linked List):在循环链表中,最后一个节点的指针不是指向null,而是指向第一个节点,形成一个环。这种链表可以无限循环遍历。

2.1.1 单链表的定义和表示

在单链表中,每个元素都是一个独立的节点,每个节点由两部分组成:数据域和指针域。数据域用于存储节点的值,指针域(通常称为“下一个指针”或“后继指针”)用于指向下一个节点。

//单链表的存储结构
typedef struct LNode
{
  Elemtype data;           //结点的数据域
  Struct LNode *next;      //结点的指针域
}LNode,*LinkList;          //LinkList为指向结构体LNode的指针类型

ps.对于初学者而言,可能有点不理解结构体中定义结构体指针这句代码,原因在于对c语言的编译过程还不是太了解。

在C语言中,定义一个结构体时,可以在定义内部使用结构体类型名来声明指向该结构体的指针。这是因为结构体类型名在结构体定义的开始就已经可见了。因此,即使在结构体定义完成之前,也可以使用 struct LNode *next; 来声明一个指向 struct LNode 的指针。

另一方面,LNode 是通过 typedef 关键字定义的 struct LNode 的一个别名。这个别名在 typedef 语句之后才有效。因此,在结构体定义内部,不能使用 LNode *next;,因为此时 LNode 别名还没有被定义。

2.1.2、单链表中的基本操作

(1)初始化

Status InitList(LinkList &L)
{
  L=new LNode;
  L->next=NULL;
  return OK;

}

这里new出来的L就是图2.8中的L,它只是一个头指针,并没有为其数据域赋值。

(2)取值

Status GetElem(LinkLIst &,int i,Elemtype &e)
{
  LNode *p=L->next;
  int j=1;
  while(p&&j<i)
  {
    p=p->next;
    ++j;
  }
  if(!p||j>i)return ERROR;
  e=p->data;
  return OK;
   
}

(3)查找(按值查找)

LNode* LocateElem(LinkList L,Elemtype e)
{
  LNode *p=L->next;
  while(p&&p->data!=e)
  {
    p=p->next;
  }
  return p;
}

在函数定义位置可以知道我们返回的是一个指针类型的数据,但是教材上的定义为LNode *Locate()

* 在LocateElem的右上角可能让很多小伙伴产生疑问?但其实在c++编译的过程中,*默认是属于前面LNode数据返回值类型的,因为函数名左上角带星号没什么意义。所以两种写法都是一样的。但是在定义指针变量的的时候*应该紧挨变量名 eg.LNode *p

(4)插入

Status ListInsert(Sqlist &L,inti,Elemtype e)
{
  LNode *p=L;
  int j=0;
  while(p && j<i-1)
  {
    p=p->next;
    ++j;
  }
  if(!p || j>i+1)    //这个地方个人认为j>i+1有点多余,根据上面代码,j不可能大于i+1,欢迎思考评论
    reruen ERROR
  s=new LNode;       //区别LNode *s
  s->data=e;
  s->next=p->next;
  p->next=s;
  return OK;
}

LNode *p 和 p = new LNode 是指针声明的两种不同用法,它们在内存分配和对象构造方面有显著差异。

  1. LNode *p: 这行代码声明了一个指向 LNode 类型对象的指针变量 p。这个声明并没有分配任何实际的内存,也没有构造任何 LNode 对象。它只是告诉编译器 p 是一个可以指向 LNode 类型对象的指针。此时,指针 p 的值是未定义的,如果尝试访问它所指向的内存,将会导致未定义行为,可能引发程序错误或崩溃。

  2. p = new LNode: 这行代码不仅声明了一个指针变量 p,还使用了 new 运算符来动态分配内存,并构造了一个 LNode 类型的对象。new LNode 会分配足够大小的内存来存储一个 LNode 对象,并调用 LNode 的构造函数来初始化这块内存。分配的内存的地址被赋值给指针 p,此时 p 指向一个新的 LNode 对象。使用 new 分配的内存储需要使用 delete 来释放,以避免内存泄漏。

简而言之,LNode *p 只是声明了一个指针,而 p = new LNode 声明了一个指针并分配了内存来存储一个 LNode 对象,同时构造了该对象。

 

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值