数据结构——线性表

前置知识

1.数据结构三要素——逻辑结构、数据的运算、存储结构(物理结构)

2.存储结构不同,数据运算的实现方式不同

逻辑结构:一个个前赴后继的信息世是线性结构。逻辑结构有集合结构、线性结构、树结构、图结构或网状结构。与数据元素本身的形式、内容、相对位置、个数无关。

存储结构:有顺序存储结构和链式存储结构。链接存储的存储结构所占存储空间分两部分,一部分存放结点值,另一部分存放表示结点间关系的指针。

3.值得注意的代码问题!!!

(1)如何判空

(2)如何判断节点p石头是表头/表尾节点(向前/后遍历的while语句)

(3)如何在表头、表中、表尾插入/删除一个节点

 4.分析时间复杂度

x=90; y=100;
while(y>0)
if(x>100)
{x=x-10;y--;}
else x++;

答案: O(1)
解释:程序的执行次数为常数阶。

for (i=0; i<n; i++)
for (j=0; j<m; j++)
a[i][j]=0;

答案: O(m*n)
解释:语句 a[i][j]=0; 的执行次数为 m*n。

s=0;
for i=0; i<n; i++)
for(j=0; j<n; j++)
s+=B[i][j];
sum=s;

答案: O(n 2)
解释:语句 s+=B[i][j]; 的执行次数为 n2。

i=1;
while(i<=n)
i=i*3;

答案: O(log 3n)
解释:语句 i=i*3; 的执行次数为 log 3n 。

x=0; 
    for(i=1; i<n; i++) 
        for (j=1; j<=n-i; j++) 
            x++; 

答案: O(n 2)
解释:语句 x++; 的执行次数为 n-1+n-2+ …+ 1= n(n-1)/2 。

x=n; //n>1 
y=0; 
while(x ≥ (y+1)* (y+1)) 
    y ++; 

答案: O( 根号n ) 解释:语句 y++; 的执行次数为 n的二分之一次方。

线性表

特点:(1)相同数据类型(2)有限序列

L=(a1,a2,,,,an)ai是位序,位序从1开始,数组下标从0开始

初始化表——分配内存空间

销毁表——释放内存空间

“&”引用型——对参数的修改结果需要返回时使用(c++)

将基本操作用函数封装起来,方便适用

顺序表——用顺序存储结构实现的线性表

sizeof(int)——返回int型数据元素占用空间大小

线性表中数据元素相同,所以占用空间相同,下移同样空间sizeof(int)就能找到下一个元素,所以知道第一个元素的位置,就能通过是sizeof知道后面元素的位置。

#include<stdio.h>
//静态分配
#define MaxSize 10
typedef struct{
    int data[MaxSize];//定义数组
    int length;//当前数据的长度
}SqList;

malloc free函数 ——malloc申请一片连续的存储空间,返回这片空间首个元素的指针(即地址)(可用new delete)

//动态分配
#include<stdio.h>
#define InitSize 10
typedef struct{
    int *data;//指示动态分配数组的指针
    int MaxSize;
    int length;//顺序表当前长度
}SqList;

int main(){
SqList L;//声明一个顺序表
InitList(L);//初始化顺序表
IncreaseSize(SqList &L ,int len)//增加动态数组长度
}

void InitList(SqList &L){
    //用malloc函数申请一片连续的存储空间
 L.data(int *)malloc(InitList*sizeof(int));
 L.length=0;
 L.MaxSize=InitSize;
}

//增加动态数组的长度
void IncreaseSize(SqList &L ,int len){
    int *p=L.data;
L.data(int *)malloc((L.MaxSize+len)*sizeof(int));
for(int i=0li<L.length;i++){
L.data[i]=p[i];
}
L.MaxSize=L.MaxSize+len;
free(p);

}

插入操作: 插入的时候先移动后面的操作,一个个往后移。

i的合法值范围 [1,length+1]

//顺序表插入
void ListInsert(SqList &L,int i,int e){
    for(int j=L.length;j>=i;j--){//将第i个元素及之后的元素后移
    L.data[j]=L.data[j-1];
    L.data[i-1]=e;
    L.length++;
    }
}

可读性更强的:

//顺序表插入
void ListInsert(SqList &L,int i,int e){
    if(i<1||i>L.length+1)
        return false;
    if(L.length>=MaxSize)
        return false;
    for(int j=L.length;j>=i;j--){
    L.data[j]=L.data[j-1];//
    L.data[i-1]=e;//在第i个位置放e
    L.length++;
    }
}

插入操作的时间复杂度

 删除操作:删除的时候先移动前面的数据,一个个往前移

//顺序表删除
void DeleteInsert(SqList &L,int i,int &e){
    if(i<1||i>L.length)
        return false;
         e=L.data[i-1];
    for(int j=i;j<L.length;j++){
    L.data[j-1]=L.data[j];
    L.length--;
    return true;
    }
}

 顺序表的查找——按位、按值

按位:用数组下标即可得到第i个元素L.data [i-1]

L.data [i-1]用数组下标的方式访问元素 

 按值:从第一个元素开始依次往后检索

//结构类型比较
bool isCustomerEqual(Customer a,Customer b)
if(a.num == b.num && a.people == b.people){
     printf("相等");
}else{
     printf("不相等");
}

 单链表——不支持随机存取,因为要用链式依次往下找

链表:链式存储结构实现线性表

//定义节点类型
struct LNode{
  ElemType data;
  struct LNode *next;//用next指针链接各个LNode
};

 typedef关键字——数据类型重命名

typedef struct LNode LNode;//将struct LNode重命名为 LNode
typedef struct LNode *LinkList;//*LinkList是指向struct LNode的指针
typedef struct LNode{
ElemType data;
  struct LNode *next;
}LNode,*LinkList;

初始化

//初始化不带头节点的单链表
bool InitLinst(LinkList &L){
   L = NULL;//判空:暂时没有节点,防止脏数据
   return true;
}

//初始化带头节点的单链表
bool InitLinst(LinkList &L){
  L = (LNode *) malloc (sizeof(LNode));//指向单链表的头指针给他分配一个头结点
  if(L == NULL)//内存不足,分配失败
      return false;
  L->next = NULL;//判空:头结点之后暂时没有节点
  return true;

}

 单链表的插入删除操作

 按位插入

//带头节点按位插入
bool ListInsert(LinkList &L,int i,ElemType e){
  if(i<1)
      return false;
  LNode *p;//指针P指向当前扫描到的节点
  int j=0;//当前P指向那个节点
  p = L;//L指向头结点
  while (p!==NULL && j<i-1){
      p=p->next;
      j++;
  }
  if(p!==NULL)
      return false;
  LNode *s = (LNode *)malloc(sizeof(LNode));
  s->data = e;
  s->next=p->next;//这两句不能颠倒
  p->next=s
      return true;
}

//不带头节点按位插入
bool ListInsert(LinkList &L,int i,ElemType e){
  if(i<1)
      return false;
  if(i==1){//插入第一位时修改头结点
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s->data = e;
    s->next=L;
    L=s;
    return true;
  }
  LNode *p;//指针P指向当前扫描到的节点
  int j=1;//当前P指向第一个节点
  p = L;//L指向头结点
  while (p!==NULL && j<i-1){
      p=p->next;
      j++;
  }
  if(p!==NULL)
      return false;
  LNode *s = (LNode *)malloc(sizeof(LNode));
  s->data = e;
  s->next=p->next;//这两句不能颠倒
  p->next=s
      return true;
}

//指定节点的前插操作
bool InsertPriorNode(LNode *p, ElemType e){
if (p==NULL)
    return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if(s==NULL)
    return false;
s->next=p->next;
p->next=s;
s->data=p->data;
p->data=e;
return true;
}

删除

//按位删除(带头节点)
bool ListDelete(LinkList &L,int i,ElemType &e){
  if(i<1)
      return false;
  LNode *p;//指针P指向当前扫描到的节点
  int j=0;//当前P指向那个节点
  p = L;//L指向头结点
  while (p!==NULL && j<i-1){
      p=p->next;
      j++;
  }
  if(p!==NULL)
      return false;
  if(p->next==NULL)
      return false;
  LNode *q=p->next;
  e=q->data;
  p->next=q->next;//这两句不能颠倒
 free(q);
      return true;
}

//删除指定节点p
bool DeleteNode(LNode *p){
if(p==NULL)
      return false;
  LNode *q=p->next;
  p->data=p->next->data;
  p->next=q->next;
  free(q);
  return true;
}

单链表查找

按位查找:获取表L中第I个位置的元素的值

在插入删除操作中可封装为LNode *p = GetElem(L,i-1);

//按位查找
LNode * GetElem(LinkList L,int i){
if(i<0)
    return NULL;
LNode *p;
int j=0;
p = L;
while (p!==NULL && j<i){
      p=p->next;
      j++;
  }
return p;
}

按值查找

//按值查找
LNode * GetElem(LinkList L,ElemType e){
LNode *p = L->next;//指向头节点的下一个节点也就是第一个节点
while(p!=NULL && p->data!=e)
    p = p->next;
return p;
}

求表长

//求表长
int Length(LinkList L){
  int len = 0;
  LNode *p = L;
  while(p->next !=NULL){
  p = p->next;
  len++;
  }
  return len;
}

单链表的建立——很多个元素存到一个单链表中

先初始化一个点链表,在每次去一个元素插入到表头/表尾

头插法

初始化单链表
while循环{
每次取一个元素e;
InterNestNode(L,e);//制定节点的后插操作

}

LinkList List_Taillnsert(LinkList &L){
    LNode *s;
    int x;
    L=(LinkList)malloc(sizeof(LNode));
    L->next=NULL;//初始为空链表,防止引入脏数据
    scanf("%d",&x);
    whilex!=9999){
s=(LNode *)malloc(sizeof(LNode));//申请新节点
s->data=x;//把S的值赋给新节点
r->next=s;//和前面的节点连接上
r=s;//r s共同指向该新节点即r指向最后一个节点
scanf("%d",&x);
}

return L;
}

重要应用 ! ! ! 头插法可实现链表的逆置

双链表

DNode * 等价于 DLinklist 前者强调链表,后者强调节点

//双链表的初始化
bool InitDLinkLIst(DLinkList &L)
{
    s=(DNode *)malloc(sizeof(DNode));
    if(L==NULL)
        return false;
    L->priot=NULL;
    L->next=NULL;
    return true;
}



typedef struct DNode{
  ElemType data;
  struct DNode *prior,*next;
}DNode,*DLinklist;//*DLinklist指向DNode的指针的类型

插入

s是要插入的节点,p是s的前驱节点

s 后向指针先指向 p后继节点的前项

p后继节点的前向指针指向s

s前项指针指向p

p后项指针指向s

//双链表的插入
bool InserNextDNode(DNode *p DNode *s){
    if(p==NULL||s==NULL)
        return false;
s->next=p->next;
if(p->next !=NULL)
p->next->prior=s;
s->prior=p;
p->next=s;
}

删除

//双链表的删除
bool DeleteNextDNode(DNode *p){
if(p==NULL) return false;
DNode *q=p->next;
if(q==NULL) return false;//p没有后继
p->next=q->next;
if(q->next!=NULL)//q节点不是最后一个节点
    q->next->prior=p;
ferr(q);
return true;
}

//销毁双链表
void DestoryList(DLinklist &L){
    while(L->next!=NULL)
    DeleteNextDNode(L);//释放各个指针
    free(L);//释放头指针
    L=NULL;//头指针指向NULL
}

循环链表

//初始化循环单链表
bool InitLinst(LinkList &L){
  L = (LNode *) malloc (sizeof(LNode));//指向单链表的头指针给他分配一个头结点
  if(L == NULL)//内存不足,分配失败
      return false;
  L->next = L;//头结点next指向头节点
  return true;

}
//初始化循环双链表
bool InitDLinkLIst(DLinkList &L)
{
    s=(DNode *)malloc(sizeof(DNode));
    if(L==NULL)
        return false;
    L->priot=L;
    L->next=L;
    return true;
}
//判空 L->next==NULL

静态链表

单链表:各个节点在内存中星罗密布

静态链表:分配一整片连续的存储空间,各个节点集中安置,是一种用数组的方式实现的链表。

游标指向的是数组下标,指针指向地址,游标设为-1代表NULL,后面没节点了

//定义静态链表
#define MaxSize 10
struct Node{   //定义结构类型
  ElemType data;
  int next;   //下一个数组的下标
};
void testSLinkList(){
struct Node a[MaxSize];//struct Node型的数组a作为静态链表
}

另一种定义方式

 typedef 将struct Node重命名为SLinkList[MaxSize],接下来可以直接定义数组SLinkList a,a就是Node型。

应用:操作系统的文件分配表FAT

顺序表与链表的比较

顺序表链表
优点支持随机存取、存储密度高离散的小空间分配方便,改变容量方便
缺点大片连续空间分配不方便,改善容量不方便不可随机存取,存储密度低
初始化需要预分配大片连续空间只需分配头结点(也可不要头结点只声明一个头指针)
销毁

静态分配:静态数组——程序自动回收空间

动态分配:动态数组(malloc、free)

free()
增删

需要将元素前移/后移

只需要修改指针
查找

按位:O(1)

按值:O(n),若表内元素有序,课通过其他算法在O(log2n时间内找到)

按位:O(n)

按值:O(n)

增删的时间复杂度都是O(n),但顺序表时间开销主要来自移动元素,链表主要来自查找元素。前者若数据元素很大,则移动的时间代价很高。

顺序表链表
扩容

×

增删

×

查找

×

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值