【数据结构】第二章 线性表

序:

本章目的:
解决线性表的计算机实现问题
2.1 线性表的定义和特点(逻辑结构)
2.2 线性表的顺序存储实现(顺序表)
2.3 线性表的链式存储实现(链表)

重点:特点、基本操作和有关算法

(1)线性表顺序存储; (2)线性表的链式存储; (3)双向链表; (4)循环链表。

难点:

线性表链式存储实现,包括单链表、双向链表、循环链表的基本操作和有关算法。

2.1线性表的类型定义(逻辑结构)

  • 线性表的定义:
    线性表是n 个类型相同数据元素的有限序列,通常记作(a1, a2, a3, …, an )。

线性表本身是一种逻辑结构

  • 线性结构的基本特征为:
    (1)集合中必存在唯一的一个“第一元素”;
    (2)集合中必存在唯一的一个 “最后元素”;
    (3)除最后元素之外,均有 唯一的后继
    (4)除第一元素之外,均有 唯一的前驱

  • 线性结构的抽象数据类型描述

ADT List
{
    数据对象:D={ ai | ai ∈ElemSet, i=1,2,...,n,  n≥0 }
                {称 n 为线性表的表长; 
                 称 n=0 时的线性表为空表。}

    数据关系:R1={ <ai-1 ,ai >|ai-1 ,ai∈D,  i=2,...,n }

    基本操作:
        InitList( &L )      //构造一个空的线性表L。
		DestroyList( &L )   //销毁线性表 L
		ListEmpty( L )        //判断栈L是否空
		ListLength( L )       //求L的长度
		PriorElem( L, cur_e, &pre_e )  //求前驱的值
		NextElem( L, cur_e, &next_e ) //求后继的值
		GetElem( L, i, &e )          //取i位置的值    
		LocateElem( L, e, compare( ) )   //在线性表中查找e
		ListTraverse(L, visit( ))   //遍历线性表
		ClearList( &L )                //清空线性表
		ListInsert( &L, i, e )        //在i位置插入e
		ListDelete(&L, i, &e)      //删除i位置的元素
}ADT List
  • 基本操作的应用

例 2-1 合并 求线性表并集

假设:有两个集合 A 和 B 分别用两个线性表 LA 和 LB 表示,
即:线性表中的数据元素即为集合中的成员。
现要求一个新的集合A=A∪B。

例2-1操作步骤:

(1) 从线性表LB中依次察看每个数据元素; ​ GetElem(LB, i,&e)//获取B中的每个e
(2) 依线性表LA中进行查访; ​ LocateElem(LA, e, equal( ))//将获取到的e从A中查访
(3) 若不存在,则插入LA中。 ​ ListInsert(LA, n+1, e)

例2-1 求线性表并集的算法

void union(List &La, List Lb) 
{
	La_len = ListLength(La);// 求线性表的长度
	Lb_len = ListLength(Lb);  
	for (i = 1; i <= Lb_len; i++) 
	{
		GetElem(Lb,i,&e);//取Lb中第i个数据元素赋给e
		if(!LocateElem(La,e,equal()))// La中不存在和e相同的数据元素,则插入之
			ListInsert(La,++La_len,e);
	}
}

例2-1算法时间复杂性

O( ListLength( La )*ListLength( Lb ) )

例 2-2 有序归并 (归并排序的基础)

将两个 “数据元素按值递增有序排列” 的线性表La和Lb归并到线性表Lc,且Lc具有同样的性质
设 La=(a1,…ai,….an)
Lb=(b1,…bj,….bm)
Lc=(c1,…ck,…cm+n)

例2-2算法 归并算法

//递增有序La和Lb归并到Lc,依旧递增有序排列
//ABC中元素的下标也是从1开始,0号不存数据
void MergeList(List La,List Lb,List &Lc)
{
    InitList(Lc);//空线性表Lc
    i=j=1;k=0;//注意是算法
    La_len=ListLength(La);
    Lb_len=ListLength(Lb);
/*
1. 分别获取 A 和 B 的元素
2. 判断元素哪个小,小的添加到 LC 中
3. 如果不是小的,那就等待下一次循环比较大小进行添加
4. 当循环结束后,分别将剩余的元素按照顺序添加进链表 LC 中 
*/ 

    while( (i<=La_len)&&(j<=Lb_len))//范围内
    {
        GetElem(La,i,&ai);//分别获取 A 和 B 的元素
        GetElem(Lb,j,&bj);
        if(ai<=bj)//判断元素哪个小,小的添加到 LC 中
        {
            ListInsert(Lc,++k,ai);//表,位置,元素
            ++i;
        }
        else
        {
            ListInsert(Lc, ++k, bj);  
            ++j;
        }
    }
    //当循环结束后,分别将剩余的元素按照顺序添加进链表 LC 中
    while(i<=La_len)//La不空
    {
        GetElem(La,i,&ai);
        i++;
        ListInsert(Lc,++k,ai);
    }
    while(j<=Lb_len)//Lb不空
    {
        GetElem(Lb,j,&bj);
        j++;
        ListInsert(Lc,++k,bj);
    }
}// merge_list

例2-2算法时间复杂性

O( ListLength( La )+ListLength( Lb ) )

2.2 线性表的顺序表示和实现(顺序表)

存储线性表,就是要保存至少两类信息

  1. 线性表中的数据元素
  2. 线性表中数据元素的顺序关系
  • 线性表的顺序表示的定义:
    以数据元素x 的存储位置和数据元素 y 的存储位置之间的某种关系表示逻辑关系<x, y>
    线性表的顺序表示最简单的一种顺序映象方法是:令x的存储位置和y 的存储位置相邻

  • 线性表的顺序表示图示:
    用一组地址连续的存储单元依次存放线性表中的数据元素。
    在这里插入图片描述

  • 数据元素地址之间的关系
    以“存储位置相邻”表示有序对<ai-1,ai>时, ai-1和ai的地址关系如下:
    LOC(ai) = LOC(ai-1) + C
    C为一个数据元素所占存储量
    所有数据元素的存储位置取决于第一个数据元素的存储位置
    LOC(ai) = LOC(a1) + (i-1)×C
    LOC(a1) 基地址

顺序表的实现

1、数据元素的数据类型Elemtype实现
方式1:

struct data
{
    char name[8];
    int age;
    char sex[2];
    float score;
};
typedef struct data ElemType;

方式2:

typedef struct data
{
    char name[8];
    int age;
    char sex[2];
    float score;
}ElemType;

方式3:

typedef struct
{
    char name[8];
    int age;
    char sex[2];
    float score;
}ElemType;

2、顺序表数据类型的实现

方式1:静态数组实现

typedef struct
{
    ElemType data[30];
    int length;// 线性表长度(现存储)
    int listsize;// 线性表规模(可容纳)
}SqList;
//SqList是一个顺序表数据类型;

Sqlist L ;//声明线性表变量L
//L.data是数组名

方式2:动态数组实现

#define LIST_INIT_SIZE 80//线性表存储空间的初始分配量
#define LISTINCREMENT 10//线性表存储空间的分配增量
typedef struct
{
    ElemType *elem;//存储空间的基址
    int length;//当前长度
    int listsize;//当前分配的存储容量 (以sizeof(ElemType)为单位)
}SqList;//顺序表

Sqlist   L;//声明线性表变量L
L.elem = (ElemType*) malloc (LIST_INIT_SIZE*sizeof (ElemType));
//以后用realloc
//L.elem是数组名

3.顺序表的优缺点

(1) 优点
​ a.节省存储空间;
​ b.对线性表中第i个结点的操作易于实现;
​ c.容易查找一个结点的前驱和后继。
​ (2)缺点
​ a.插入和删除操作需要移动数据;
​ b.建立空表时,较难确定所需的存储空间

算法:

算法: 构造一个空的线性表

算法:

Status InitList_Sq(SqList &L)
{
    L.elem=(ElemType*)malloc(LIST_INIT_SIZE*sizeof (ElemType));
    if (!L.elem) 
		exit(OVERFLOW);
    L.length = 0;
    L.listsize = LIST_INIT_SIZE;
    return OK;
}// InitList_Sq

程序:(几乎一致)

//初始化 
int initSq(SqList &L)
{
	L.elem=(Elemtype *)malloc(LIST_INIT_SIZE*sizeof(Elemtype));//初分配
	if(!L.elem )
		return 0;
	L.listsize=LIST_INIT_SIZE; 
	L.length=0;
	return 1;
}//initSq

注意!
应用时:先声明线性表变量Sqlist L;
然后调用函数InitList_Sq(L);

题:

已定义初始化函数Status InitList_Sq( SqList &L ) {……} 下面哪种调用方式是正确的 D

SqList &L; InitList_Sq( L)

SqList L; InitList_Sq( *L)

SqList *L; InitList_Sq( L)

SqList P; InitList_Sq( P ) 正确

算法---- 销毁线性表

算法:

Status DestoryList(SqList &L)
{
    free(L.elem);
    return OK;
}

程序:(一致)

//摧毁线性表
int destorySq(SqList &L) 
{
	free(L.elem);
	return 0;
}

算法---- 判断顺序表是否为空

算法:

Status ListEmpty(SqList L) 
{
	//return (L.length==0)
	if(L.length==0)
		return TRUE;
	else
		return FALSE;
} // InitList_Sq

程序:(一致)

//判断顺序表是否为空
int listEmpty(SqList L)
{
	if(L.length==0)
		return 1;
	else
		return 0;
}

算法---- 顺序表求前驱的值PriorElem

算法:程序:(一致)

Status PriorElem(SqList L,int cur_e,ElemType &pre_e)//线性表L,当前元素的位置,前驱元素的值
{
    if(cur_e<=0||L.length==0||cur_e>L.length)//容错
        return ERROR;
    else
        pre_e=L.elem[cur_e-1];
}

算法:顺序表长度

算法:程序:(写法一致)

Status ListLength( SqList L ) 
{
	return  L.length;
} // InitList_Sq

算法:顺序表插入

算法:

//防止忘记存储结构
typedef struct
{
    ElemType *elem;//存储空间的基址
    int length;//当前长度
    int listsize;//当前分配的存储容量
}SqList;//顺序表
//插入
Status ListInsert_Sq(SqList &L,int i,ElemType e )//在L的第i个元素之前插入e
{
    if(i<1 || i>L.length+1 )
        return ERROR;//位置不合法
    if(L.length >=L.listsize )// 当前存储空间已满,增加分配
    {
        newbase=(ElemType *)realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(ElemType));//这是算法
        if(!newbase)
            exit(OVERFLOW);
        L.elem=newbase;//新基址
        L.lisesize+=LISTINCREMENT;
    }
    //数组实现
    for(j=L.length-1;j>=i-1;--j)//从最后开始
    {
        L.elem[j+1]=L.elem[j];//插入位置及之后的元素后移
    }
    L.elem[i-1]=e;//插入e
    ++L.length;//表长增加1
    return OK;
    
    //指针实现
    q=&(L.elem[i-1]);
    for(p=&(L.elem[L.length-1];p>=q;p--))
    {
        *(p+1)=*p;//后移
    }
    *q=e;//
    ++L.length;//
    return OK;
}//ListInsert_Sq

程序:

//插入 输入的是序号 0号不存 序号=下标
int insertSq(SqList &L,int pos,Elemtype e)//在L的第i个元素之前插入
{//新元素应该在L.elem[i-1]这个下标 

	if(pos<1 || pos>L.length+1 )//位置不合法
		return 0;
	if(L.length>=L.listsize )//当前存储空间已满,增加分配
	{
		L.elem=(Elemtype*)realloc(L.elem, (L.listsize+LISTINCREMENT)*sizeof(Elemtype));
		if(!L.elem )return 0;
		L.listsize=L.listsize+LISTINCREMENT;
	}
	//插入 倒序
	//1.数组实现 
	for(int j=L.length-1 ; j>=pos-1;j-- )//跟算法不同在于变量j需要声明
	{
		L.elem[j+1]=L.elem[j];//插入位置及之后的元素后移
	}
	L.elem[pos-1]=e;
	L.length++;
	
	//2.指针实现 
	
	Elemtype* q=&(L.elem[pos-1]);//应该在这个位置 
	for( Elemtype* p=&(L.elem[L.length-1]; p>=q; p--))//从最后一个元素 
	{
		*(p+1)=*p;//后移 
	} 
	*q=e;
	L.length++;

	return 1; 
}

插入算法时间复杂性分析

设在第i个元素之前插入的概率为pi,则在长度为n的线性表中插入一个元素时所需平均移动元素次数为:
在这里插入图片描述
若假定在任何一个位置上插入元素的概率相同,则平均移动次数为: 在这里插入图片描述
算法:线性表删除——判断删除位置
算法:程序:(差一个变量的定义,其他一样)

Status ListDelete_Sq(SqList &L,int i,ElemType &e)
{
    if( i<1 || i>L.length)
        return ERROR;
    
    //数组实现
	e=L.elem[i-1];//e记录删除的元素
    for(j=i-1; j<L.length-1; j++)
    {
        L.elem[j]=L.elem[j+1];//后一位赋值给当前位
    }
    --L.length;//表长-1
    return OK;
        
    //指针实现
    //程序多了一个定义变量Elemtype *p;
    p=&(L.elem[i-1]);//p为被删除元素的位置
    e=*p;
    q=L.elem + L.length -1;//表尾元素的位置
    for(++p;p<=q;++p)//从删除元素之后一直到表尾
    {
        *(p-1)=*p;//后一位赋值给当前位
    }
    --L.length;
    return OK;
}
  • 删除操作时间复杂性分析
    设删除第i个元素的概率为pi,则在长度为n的线性表中删除一个元素时所需平均移动次数为:在这里插入图片描述

若假定在任何一个位置上删除元素的概率相同,则平均移动次数为:在这里插入图片描述

程序:遍历输出

void traverseSq(SqList L)
{
	cout<<"\n---traverseSq----\n";
	for(int i=0;i<L.length;i++)
	{
		cout<<"age:"<<L.elem[i].age<<" sex:"<<L.elem[i].sex <<endl;
	}
}

2.3 线性表的链式表示和实现(链表)

2.3.1 单链表

  • 单链表结构示意图
    单链表结构示意图

  • 结构特点 :
    逻辑次序和物理次序不一定 相同;
    元素之间的逻辑关系用 指针表示;需要额外空间存储元素之间的关系

  • 链表的定义
    一个线性表由若干个结点组成,每个结点至少含有两个域数据域(信息域)指针域(链域),由这样的结点存储的线性表称作链表

  • 指针的概念
    在链式存储结构中以第一个结点的存储地址作为线性表的基地址,通常称它为头指针
    线性表的最后一个数据元素没有后继,因此最后一个结点中的“指针域"是一个特殊的值 “NULL”,通常称它为"空指针"。

  • 结点的概念
    在单链表上设置一个结点,它本身不存放数据,它的指针域指向第一个元素的地址。头结点

  • 头结点的作用
    使对第一个元素的操作与对其它元素的操作保持一致。

单链表的C语言实现

0

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值