820数据结构(2)线性表

考纲

  1. 线性表的定义
  2. 线性表的基本操作及在顺序存储及链式存储上的实现
  3. 各种变形链表(循环链表、双向链表、带头结点的链表等)的表示和基本操作的实现
  4. 递归过程的特点及实现方法
  5. 栈和队列的基本概念;栈和队列的顺序存储结构、链式储存结构及其存储特点
  6. 栈和队列的应用
  7. 循环队列的判满、判空方法
  8. 特殊矩阵的压缩储存

一、线性表的定义

1、定义(了解)

  • 线性表是具有相同数据类型的 n(n≥0) 个数据元素的有限序列。n为表长,空表:n=0。

2、特点(了解)

  • 表中元素:
    • 个数有限
    • 有逻辑上的顺序性
    • 是数据元素
    • 数据类型相同,占有相同大小的存储空间
    • 具有抽象性(逻辑关系)

3、tips

  1. 区分:线性表是逻辑结构,表示元素间一对一的相邻关系。顺序表和链表指存储结构。

二、线性表的基本操作及在顺序存储及链式存储上的实现

(一)顺序表(帮助理解时间复杂度,不掌握)

(1)顺序表存储结构

  • 假设线性表L存储的起始位置为LOC(A),sizeof(ElemType)是每个数据元素所占用的存储空间的大小,则L所对应的顺序存储如下:
    在这里插入图片描述
  • 顺序存储类型分配语句
    • 静态分配
      #define MaxSize 50
      typedef struct{
      	ElemType data[MaxSize];       //顺序表的元素
          int length;                   //顺序表当前长度
      }SqList;                          //顺序表类型定义
      
    • 动态分配
      #define InitSize 100
      typedef struct{
      	ElemType *data;              //指示动态分配数组的指针
          int length,MaxSize;          //当前个数和最大容量
      }SeqList;                        //顺序表类型定义
      
    • 动态分配语句
       L.data = (ElemType*)malloc(sizeof(ElemType)*InitSize);
      

(2)顺序表的基本操作

  1. 分配
//头文件
#include<stdio.h>
#include<stdlib.h>
//状态量
#define OK 1    
#define ERROR 0 
typedef int Status;	          //Status是函数的类型,其值是函数结果状态代码

#define MAXSIZE 100           //初始分配量
typedef int ElemType;         //ElemType的类型根据实际情况而定
typedef struct{
    ElemType *elem;           //存储空间基址,动态分配
    //ElemType data[MAXSIZE]  //静态分配
    int length;               //存放顺序表的长度
}SqList; 

注(天勤):考试中用得最多的顺序表定义并不是结构型定义,而是如下:

int A[MAXSIZE];    //存储表中元素的数组A,类型可变
int n;
  1. InitList(&L)

初始化表。构造一个空的线性表

Status InitList(SqList* L){
  L -> elem = (ElemType *)malloc(MAXSIZE*sizeof(ElemType));
  if(!L -> elem) exit(ERROR);
  L -> length = 0;
  return OK;
}
  1. Length(L)

求表长,返回线性表L的长度,即L中数据元素的个数

int Length(SqList L){
    return L.length;
}
  1. LocateElem(L,e)

按值查找。在L中找具有给定关键字值的元素

顺序表:平均时间复杂度O(n)(表头O(1),表尾O(n))
在这里插入图片描述

Status LocalElem(SqList L, ElemType *e){
    int i;
    for(i=0; i<L.length; i++){
        if(L.elem[i] == e)
            return i+1;          //返回第几位
    }
    return ERROR;                //查找失败
}
  1. GetElem(L,i)

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

Status GetElem(SqList L, int i, ElemType *e){
    if(L.length==0 || i<1 || i>L.length){
        return ERROR;                    //判断范围
    }
    *e = L.elem[i-1];
    return OK;
}
  1. ListInsert(&L,i,e)

插入操作。在L中第i个位置上插入指定元素e。

顺序表:平均时间复杂度O(n)。(表尾O(1),表头O(n))
在这里插入图片描述

Status ListInsert(SqList *L, int i, ElemType e){
    if (L->length >= MAXSIZE){       //存储空间已满
        return ERROR;
    }
    if (i<1 || i>L->length+1){       //判断i范围是否有效
        return ERROR;
    }
    for(int j=L->length; j>=i; j--){ //将第i个及之后的元素后移
        L->elem[j] = L->elem[j-1];
    }
    L->elem[i-1] = e;                //i处放入w
    L->length++;                     //长度加1
    return OK;
}
  1. ListDelete(&L,i,e)

删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。

顺序表:平均时间复杂度O(n)。(表尾O(1),表头O(n))
在这里插入图片描述

Status ListDelete(SqList *L, int i, ElemType *e){
    if(L->length == 0){                  //线性表为空
        return ERROR;
    }
    if(i<1 || i>L->length){              //i范围是否有效
        return ERROR;
    }
    *e = L->elem[i-1];
    for(int j=i; j<L->length; j++){
        L->elem[j-1] = L->elem[j];
    }
    L->length--;                         //长度减1
    return OK;
}
  1. PrintList(L)

输出操作。按前后顺序输出线性表L的所有元素值。

void PrintList(SqList L){
    for(int i=0; i<L.length; i++){
        printf("%d ",L.elem[i]);
    }
    printf("\n");
}
  1. Empty(L)

判空操作。若L为空表,则返回true,否则返回false。

Status Empty(SqList L) {
	if (L.length == 0)
		return OK;
	else
		return ERROR;
}
  1. DestroyList(&L)

销毁操作。销毁线性表,并释放线性表L所占用的内存空间。

void DestroyList(SqList *L){
    free(L->elem);
    L->elem = NULL;
    L->length = 0;
}

区分:清空操作

Status ClearList(SqList *L){
    L -> length = 0;
    return OK;
}

(二)单链表(掌握)

1. 单链表存储结构

  • 带头结点
    在这里插入图片描述
typedef int ElemType;
//构造结点
typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;
  • LNode *:强调这是一个结点
  • LinkList:强调这是一个单链表

2. InitList(&L)

//初始化带头结点
Status InitList(LinkList L){
    L = (LinkList)malloc(sizeof(LNode));
	if (L == NULL)            //内存不足分配失败
		return ERROR;
	L->next = NULL;          //将单链表的头结点指针域为空
	return OK;
}
//初始化不带头结点
Status InitList(LinkList *L){
    L = NULL;            //空表,暂时还没有任何结点,防止脏数据
    return OK;
}

3. Length(L)

时间复杂度O(n)

//带头结点
int Length(LinkList *L){
    int len=0;        //统计表长
    LNode *p = L;
    while(L!=NULL){
        p=p->next;
        len++;
    }
    return len;
}
//不带头结点
int Length(LinkList L)
{
	int len = 0;
	if(L == NULL)//首先判断第一个结点是否为空
		return len;
	else
		len++;
	while(L->next != NULL){
		len++;
		L = L->next;
	}
	return len;
}

4. LocateElem(L,e)

时间复杂度O(n)

//带头结点
LNode* LocateElem(LinkList L, ElemType e){
	LNode *p = L->next;		     //指针p指向第一个结点
	while (p!=NULL && p->data!=e)//从第一个结点开始查找data域为e的结点
        p = p->next;	         //查到最后一个元素为NULL为止
	return p;				     //返回查出来的指针p
}
//不带头结点
LNode* LocateElem(LinkList L, ElemType e){
	LinkList p = L;
	int j = 0;
	while (p){
		j++;
		if (p->data == e)
			return p;
		p = p->next;            //更新p
	}
	return NULL;                //查找失败,返回-1
}

5. GetElem(L,i)

时间复杂度O(n)

//带头结点
LNode *GetElem(LinkList *L, int i){
	if (i<0)         //判断要插入的结点位置是否存在
		return NULL;
    LNode *p;
    int j=0;
    p=L;
    /*王道书版本
    int j=1;
    LNode *p = L->next;
    if(i==0)
        return L;
    if(i<1)
        return NULL;
    */
	while (p!=NULL && j<i){//通过循环找到要找的第i个结点
		p = p->next;
		j++;
	}
    return p;//返回指针p
}
//不带头结点(待测)
ElemType GetElem(LinkList L, int i){
	LNode *p = L;
	if(i < 0 || i > Length(L))
		return NULL;
	for(int j = 1; j < i; j++){
		p = p->next;
	}
	return p;
}

6. ListInsert(&L,i,e)

(1)头插法(关键代码while内可能单独选填)

在这里插入图片描述

时间复杂度:每点插入时间为O(1),表长n则总复杂度为O(n)

//头插法建立单链表(有头结点)
LinkList List_HeadInsert(LinkList L){ //逆向建立
    LNode *s;
    ElemType x;
    //代替:InitList(L);               //创建头结点,初始为空
    L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;                    //初始为空【尾插可要可不要】
    //代替结束
    scanf("%d",&x);
    while(x!=9999){                    //输入9999表示结束
        s = (LNode*)malloc(sizeof(LNode));
        s->data = x;
        s->next = L->next;
        L->next = s;                   //将新结点插入表中,L为头指针
        scanf("%d",&x);
    }
    return L;
}
//不带头结点(待测试)
LinkList List_HeadInsert(LinkList L){
	InitList(L);
	LNode *s;
	ElemType x;
	scanf("%d",&x);
	if(x != 9999){                          //对第一个结点赋值
		L = (LinkList)malloc(sizeof(LNode));
		L->next = NULL;
		L->data = x;
		scanf("%d",&x);
		while(e != 9999){
			s = (LNode*)malloc(sizeof(LNode));
			s->data = x;
			s->next = L;
			L = s;
			scanf("%d",&x);
		}
	}
	return L;
}
(2)尾插法(关键代码while内可能单独选填)

在这里插入图片描述

时间复杂度和头插法一致

//尾插法建立单链表(带头结点)
LinkList List_TailInsert(LinkList L){ //正向建立
    ElemType x;
    L = (LinkList)malloc(sizeof(LNode));//建立头结点
    LNode *s,*r=L;                      //r为表尾指针
    scanf("%d", &x);
    while(x!=9999){
        //后插思维
        s = (LNode*)malloc(sizeof(LNode));
        s->data = x;
        r->next = s;
        r = s;                          //r指向新的表尾结点
        scanf("%d", &x);
    }
    r->next = NULL;                     //尾结点指针置空
    return L;
}
//不带头结点(待测试)
LinkList List_TailInsert(LinkList L){
	InitList(L);
	LNode *s,*r;
	ElemType x;
	scanf("%d",&x);
	if(e != 9999){                        //对第一个结点赋值
		L = (LinkList)malloc(sizeof(LNode));
		L->data = x;
		r = L;
		scanf("%d",&x);
		while(e != 9999){
			s = (LNode*)malloc(sizeof(LNode));
			s->data = x;
			r->next = s;
			r = s;
			scanf("%d",&x);
		}
	}
	r->next = NULL;
	return L;
}
(3)前插

在这里插入图片描述

查找第i-1个元素时时间复杂度O(n)。若在给定的结点后面插入新结点,则时间复杂度仅为O(1)。

//前插(在p结点前插入)
Status InsertPriorNode(LNode *p, ElemType e){
    if(p==NULL)             //GetElem返回NULL时
        return ERROR;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    if(s==NULL)
        return ERROR;
    s->next = p->next;
    p->next = s;             //新结点s连到p之后
    s->data = p->data;       //将p中元素复制到s中
    p->data = e;             //p中元素覆盖为e
    return OK;
}
/*//王道书版本
Status InsertPriorNode(LNode *p,LNode *s){
    if(p==NULL || s==NULL)
        return ERROR;
    s->next = p->next;
    p->next = s;              //s连到p之后
    ElemType temp = p->data;  //交换数据域部分
    p->data = s->data;
    s->data = temp;
    return OK;
}*/
(4)后插
//后插(在p结点后插入)
Status InsertNextNode(LNode *p, ElemType e){
    if(p==NULL)
        return ERROR;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    if (s==NULL)
        return ERROR;
    //代替:p = GetElem(L,i-1);
    s->data = e;            //保存e
    s->next = p->next;      //将结点s连到p之后
    p->next = s;
    return OK;
}
(5)按位序插入(带/不带头结点)
//按位序插入(带头结点)(//不带头结点)【前插】
LinkList ListInsert(LinkList L, int i,ElemType e){
    if(i<1)
        return ERROR;
    /*不带头结点:需要对在第一个结点插入时进行特殊处理
    if (i==1){      //需要更改头指针
        LNode *s = (LNode *)malloc(sizeof(LNode));
        s->data = e;
        s->next = L;
        L = s;
        return OK;
    }
    */
    //代替:LNode *p = GetElem(L, i-1);//找到第i-1个结点
    LNode *p;        //指针p指向当前扫描到的结点
    int j=0;         //当前p指向的是第几个结点
    //int j=1;       //不带头结点时
    p = L;           //L指向头结点,头结点是第0个结点(不存数据)
    while(p!=NULL && j<i-1){    //循环找到第i-1个结点
        p = p->next;
        j++;
    }
    //代替结束
    //代替:return InserNextNode(p,e);
    if(p==NULL)               //i不合法
        return ERROR;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s->data = e;
    s->next = p->next;
    p->next = s;                //将结点s连到p之后
    return OK;
    //代替结束
}

7. ListDelete(&L,i,e)

(1)按位序删除

在这里插入图片描述

//按位序删除(带头结点)
Status ListDelete(LinkList *L, int i, ElemType *e){
    if(i<1)
        return ERROR;
    //代替:LNode *p = GetElem(L, i-1);//找到第i-1个结点
    LNode *p;
    int j = 0;
    p = L;
    while(p!=NULL && j<i-1){    //循环找到i-1个结点
        p = p->next;
        j++;
    }
    //代替结束
    if(p == NULL)
        return ERROR;
    if(p->next = NULL)           //第i-1个结点后无别的结点
        return ERROR;
    LNode *q = p->next;          //q指向被删除结点
    e = q->data;                 //用e返回值
    p->next = q->next;           //将*q结点从链中断开
    free(q);                     //释放空间
    return OK;
}
//不带头结点(待测)
Status ListDelete(LinkList *L, int i, ElemType *e)
{
	if(i<1 || i>Length(L))//位置不在表中则返回false
		return ERROR;
	if(L==NULL)//表为空则返回false
		return ERROR;
	if(i==1){
		LNode* p = L;
		e = p->data;
		LNode* q = L->next;
		free(p);//释放p结点
		L = q;
		return OK;
	}
	LNode *p = GetElem(L, i-1);
	LNode *q = p->next;
	e = q->data;
	p->next = q->next;
	free(q);
	return OK;
}
(2)按节点删除
//删除指定结点p(局限性:无法逆向检索)
Status DeleteNode(LNode *p){
    if(p == NULL)
        return ERROR;
    LNode *q = p->next;           //q指向*p的后继结点
    p->data = p->next->data;      //和后继结点交换数据域
    p->next = q->next;            //*q结点从链中断开
    free(q);                      //释放后继结点的存储空间
    return OK;
}

8. PrintList(L)

void PrintList(LinkList L){
    while(L){
        printf("%d ",L->data);
        L=L->next;
    }
    printf("\n");
}

9. Empty(L)

//判断是否为空(不带头结点)
Status Empty(LinkList L){
    if (L == NULL)
        return OK;
    else
        return ERROR;
}
/*或者
Status Empty(LinkList L){
    return(L==NULL);
}*/

10. DestroyList(&L)

void DestroyList(LinkList L) {
	LinkList p = L;
	while (p){
		L = L->next;
        free(p);
		p = L;
	}
}

(三)双链表

在这里插入图片描述

1. 结点类型

typedef struct DNode{
	ElemType data;              //数据域
	struct DNode *prior,*next;  //前驱和后继指针
}DNode, *DLinklist;

2. 插入操作(选填)

将*s插入*p后
s->next = p->next; //将结点*s插入到结点*p之后
p->next->prior = s;
s->prior = p;
p->next = s;

顺序不唯一,但①②两步必须在④步之前。否则*p的后继结点的指针就会丢掉。

3. 删除操作(选填)

删除*p的后继结点*q
p->next = q->next;
q->next ->prior = p
free(q)

(四)循环单链表(选填)

在这里插入图片描述

  • “对循环单链表不设头指针而仅设尾指针,从而使操作效率更高。”
    • 原因:若设的是头指针,对表尾进行操作需要O(n)的时间复杂度,而若设的是尾指针r,r->next即为头指针,对表头与表尾进行操作都只需要O(1)的时间复杂度。

(五)循环双链表

在这里插入图片描述

(六)静态链表

(1)静态链表结构类型

#define MaxSize 50
typedef struct{          
	ElemType data;        //存储数据元素
	int next;             //下一个元素的数组下标
}SLinkList[MaxSize];

(2)静态链表和单链表的对应关系(推导)

在这里插入图片描述

三、顺序表和链表对比、优缺点

(一)比较

在这里插入图片描述

(二)实际选取

在这里插入图片描述
通常较稳定的顺序表选择顺序存储,而频繁进行插入、删除的线性表(即动态性较强)宜选择链式存储。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值