数据结构学习笔记_线性表

3 篇文章 0 订阅
2 篇文章 0 订阅

学习数据结构知识的同时,梳理知识,也便于以后查找
tags:《数据结构(c语言版)》、B站视频
线性表、单链表

线性表

线性结构是一个数据元素的有序(次序上的)集。

线性表数据结构中的一种,是由n个类型的数据元素组成的有限序列,具有前驱和后继,非空线性表还具有开始结点和终端结点。

在稍复杂的线性表中,一个数据元素可以由若干个数据项组成。在这种情况下,常把数据元素称为记录,含有大量记录的线性表又称为文件

线性表的两种实现方式

顺序表示(顺序表)

  • 概念:用一组地址连续的存储单元依次存储线性表的数据元素,这种存储结构的线性表称为顺序表。

  • 特点:逻辑上相邻的数据元素,物理次序也是相邻的。

    只要确定好了存储线性表的起始位置,线性表中任一数据元素都可以随机存取,所以线性表的顺序存储结构是一种随机存取的储存结构,因为高级语言中的数组类型也是有随机存取的特性,所以通常我们都使用数组来描述数据结构中的顺序储存结构,用动态分配的一维数组表示线性表。

设线性表中每个元素需占用l个存储单元,则信息必须中元素满足:LOC(a(i+1)) = LOC(ai) + l,;第i个数据元素ai的存储位置LOC(ai) = LOC(a1) + (i-1)* l。第一个数据元素的地址被称为基地址
线性表的这种机内表示称做线性表的顺序存储结构或顺序映像。换句话说,以元素在计算机内物理位置相邻来表示线性表中数据元素之间的逻辑关系

代码实现

以最简单的学生信息管理为例:

  • 首先先创建两个数据结构,如下
#define maxsize 100 //定义学生最大数量
#define OK 1       //正确标志
#define ERROR 0     //失败标志
//学生信息的数据结构
typedef struct
{
    int id;   //学生id
    char name[30];   //学生姓名
    
}Student;
 
 
//顺序表数据结构
typedef struct
{
    Student *elem;   //储存空间的基地址
    int length;      //数据结构的长度
}SqList;
 
 
//定义SqList类型的变量
SqList L;

这是一个十分简单的例子,这样我们就可以通过L.elem[i-1]访问序号为i的学生信息了。其实这里我们用到了指针数组。

  • 基本算法
  //初始化,顺序表基本算法
    Status InitList(SqList &L)
    {
        //构造一个空的顺序表L
        L.elem = new ElemType[maxsize];  //分配内存空间
        if(!L.elem) exit(-1);
        L.length = 0;
        return OK;
    }
    
 //顺序表取值
   Status Get(SqList &L,int i,ElemType &e)
   {
     if(i<1||i>L.length)  return ERROR;
     e = L.elem[i-1];
     return OK;  
       
   }

//查找,顺序表查找
   int Find(SqList L,ElemType e)
   {
       //查找值为e的数据元素,返回其序号
       for(i=0;i<L.length;i++)
       {
           
           if(L.elem[i]==e) return i+1;
         
       }
       return ERROR;   //查找失败
       
   }

//插入,顺序表插入
   Status ListInsert(SqList &L,int i,ElemType e)
   {
       if((i<1)||(i>L.length+1)) return ERROR;  //i不合法
       if(L.length == maxsize) return ERROR;  //满了
       for(j=L.length-1;j>=i-1;j--)
       L.elem[j+1]=L.elem[j]; //将第n个至i个位置的元素后移
       L.elem[i-1]=e; //将e放进第i个位置
   }

 //删除,顺序表删除
  Status ListDelete(SqList &L,int i)
  {
      //删除第i个元素,i的值为[1,L.length]
      if((i<1)||(i>L.length)) return ERROR;
      for(j=i;j<=L.length-1;j++)
        L.elem[j-1]=L.elem[j];
      --L.length;  //长度减一
      return OK;
  }

算法分析

  • 算法都十分的简单,但为啥有的参数用的是引用,有的不是呢?

这里讲下使用引用作为形参的作用了,主要有三点:

  1. 使用引用作为参数与使用指针作为参数的效果是一样的,形参变化时实参对应也会变化,这个我在上篇文章(我上面给的链接)也有说明,引用只是一个别名。
  2. 引用类型作为形参,在内存中并没有产生实参的副本,而使用一般变量作为形参,,形参和实参会分别占用不同给的存储空间,当数据量较大时,使用变量作为形参可能会浪费时间和空间。
  3. 虽然使用指针也可以达到引用一样的效果,但是在被调函数中需要重复使用"*指针变量名"来访问,很容易产生错误并且使程序的阅读性变差。

此时你会发现,使用顺序表作为存储时,空间是一次性直接开辟的,所以可能会有空间不足或者浪费空间的情况出现,那么为啥不用一个就分配一个空间呢,再使用一个方式将这些空间串起来不就好了,是时候展现真正的技术了(链表)。

线性表的链式表示和实现

概念:用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的),包括数据域和指针域,数据域存数据,指针域指示其后继的信息

单链表可由头指针唯一确定。
由于最后一个元素没有直接后继,则线性链表最后一个结点的指针为空(NULL)

用线性链表表示线性表时,数据元素之间的逻辑关系是由结点中的指针指示的。

线性链表

  • 没有头结点的单链表
  • 带头结点的单链表,头指针指向头结点的非空指针
  • 对第一个结点的操作不再特殊
  • 空表和非空表的处理也就统一
头指针、头结点和首元结点
  • 头指针是指向链表的第一个结点(或为头结点或为首元结点)的指针
    • 单链表可由头指针唯一确定
  • 头结点是在链表的首元结点之前附设的一个结点,数据域只存放空表标准和表长等信息
  • 首元结点是链表在存储线性表第一个数据元素a1的结点
头指针->头结点->首元结点
代码实现
  • 合并单链表。按书上的合并两个单链表为一个有序链表
//书上,合并两个单链表为一个有序链表
void MergeList_L(LinkList &La, LinkList &Lb, LinkList &Lc ){
    /**
     * 单链表La、lb元素按值非递减排列
     * 归并单链表La、lb得到单链表Lc,lc的元素按值非递减排列
    */
    pa = La->next; pb = Lb->next;
    //用la作Lc的头结点
    Lc = pc = La;
    while(pa && pb){
        if(pa->data <= pb->next){
            pc->next = pa; pc = pa; pa = pa->next;
        }
        else{
            pc->next = pb; pc = pb; pb = pb->next;
        }
    }  
    //插入剩余段
    pc->next = pa ? pa : pb;
    //释放Lb的头结点
    free(Lb)
}//MergeList_L
  • 完整代码
#include<stdio.h>
//存储结构
typedef int ElemType;//起一个别名
typedef struct node{//定义链表结点类型
    ElemType data;//数据域
    struct node *next;//指针域
}LNode,slink,*LinkList; //单链表类型名

//头插法
LNode *Creatslink_Head(int n){
    LNode *head,*p;
    int i;
    if(n<1) return NULL;

    head = NULL;

    for( i = 1; i <= n; i++)//建立n个结点的单链表
    {
        p = (LNode *)malloc(sizeof(LNode));
        if(p == NULL){//内存申请失败,错误处理
            printf("内存申请失败!");
            return 0;
        }
        scanf("%d",&p->data);
        p->next = head;
        head = p;
    }
    p = (slink*)malloc(sizeof(slink));
    if(p == NULL){//内存申请失败,错误处理
        printf("内存申请失败!");
        return 0;
    }
    p->next = head;
    head = p;//头结点
    return (head);   //返回头指针 
}

//尾插法/*增加一个尾指针*/
LNode *Creatslink_End(int n){
    LNode *head,*p,*r; 
    if(n<1) return NULL;
    int i;

    r = head = (LNode *)malloc(sizeof(LNode));//头结点
    if(r == NULL){//内存申请失败,错误处理
        printf("内存申请失败!");
        return 0;
    }
    
    for( i = 1; i <= n; i++)//建立n个结点的单链表
    {
        p = (LNode *)malloc(sizeof(LNode));
        if(p == NULL){//内存申请失败,错误处理
            printf("内存申请失败!");
            return 0;
        }
        scanf("%d",&p->data);
        r->next = p;    r = p;       
    }
    r->next = NULL;//处理尾结点
    return (head);   //返回头指针 
}

//按序号查找
LNode *LocateNumber(LNode *head,int i){//返回第i个结点的指针
    int j = 1;
    LNode *p;
    p = head->next;//让p指向第一个结点

    while (p->next && j < i)//下一结点不为空,且没到i
    {
       p = p->next;
       j++;
    }
    if(i == j){
        return p;
    }else
    {
        return NULL;
    }
    
}

//按值查找
LNode *LocateValue(LNode *head,ElemType x){
    LNode *p;
    p = head->next;//让p指向第一个结点

    while (p && p->data != x)
    {
        p = p->next;
    }
    if (p == NULL)//找到最后一个结点的指针为空
    {
        return 0;
    }
    return p;  
}

//插入
LNode *Insert(LNode *head,int i,ElemType x){
    //在第i个结点之前插入值为x的新结点
    LNode *p,*q;
    int j = 0;
    if(i < 1) return 0;
    q = head;
    while(p && j < i-1){//找第i-1个结点
        p = p->next;j++;
    }
    if(p == NULL) return 0;//i值超过表长+1
    q = (LNode *)malloc(sizeof(LNode));
    if(p == NULL){//内存申请失败,错误处理
        printf("内存申请失败!");
        return 0;
    }
    q->data = x;
    q->next = p->next;
    p->next = q;

    return 1;
}

//删除
LNode *DeleteNode(LNode *head,int i,ElemType *e){
    //删除单链表中第i个结点
    slink *p,*q;
    int j = 0;
    p = head;
    while(p->next && j < i-1){
        p = p->next;
        j++;
    }
    if(p->next == NULL || j > i-1){//当i>n或i<1时,删除位置不合理
        return 0;
    }
    q = p->next;//q指向被删除结点
    p ->next = q->next;
    *e = q->data;
    free(q);
    return 1;
}

//输出链表
void ShowLinklist(LNode *head)
{
	LNode *p;
 
	p = head;
	int len = LinkList_length(head);
	printf("单链表长度: ");	printf("%d\n",len);
	while(p != NULL)
	{
		printf("%d ", p->data);
		p = p->next;
	}
	printf("\n");
}

int LinkList_length(LNode *head){//计算链表长度
    int length=0;
    LNode *p;
    p = head;
	while(p->next){
		p = p->next;
		length++;
    }
    length /= 2;
	return length;
}


//查找链表中倒数第k个位置上的结点(k为正整数)。
//若查找成功,算法输出该结点的data值,并返回1;
//否则,只返回0。
int Search(LNode *head,int k){
    LNode *p;
    p = head;
    int len = LinkList_length(p);
    int i = 0;//计数器
    while (p->next)
    {
        if(len-k == i+1){
            printf(p->data);
            return 1;
        }
        i++;
    }
    if(p->next == NULL || i > len-1){//当i>n或i<1时,删除位置不合理
        return 0;
    }

    return 0;
}

//高效算法
//该链表只给出了头指针list。在不改变链表的前提下,尽可能快
int HighSearch(LNode *list,int x){
    int len = LinkList_length(list);//获得链表长度
    if(x > len) return 0;

    LNode *q,*p; 
    int num = 0;//计算器
    q = p = list->next;//让p、head指向第一个结点

    while (p && p->next == NULL)//p指针达到链表尾
    {   
        if(num >= x){
            q = q->next;
        }
        p = p->next;
        num++;
    }

    return 0;
}

//使用双指针,实现就地逆置
void Inverse(LNode *head) {//就地逆置
    LNode *p, *q;
    p = head->next;
    head->next = NULL;
    while (p != NULL) {
        q = p;
        p = p->next;
        q->next = head->next;
        head->next = q;
    }
}

int main(void){
    int choice;
	LNode *head;
 
	//head = (linklist*)malloc(sizeof(linklist));
	while(1)
	{
		printf("单链表的创建\n");
		printf("1.使用尾插法创建单链表\n");
		printf("2.就地逆置\n");
		printf("3.链表输出显示\n");
		printf("4.退出\n");
		
		printf("做出选择:\n");
		scanf("%d",&choice);
		switch(choice)
		{
		//尾插法
		case 1:
			head = CreateList_End();
			break;
		//就地逆置
        case 2:
            Inverse(head); 
			break;
		//输出链表
		case 3:
			ShowLinklist(head);
			break;
		//退出程序
		case 4:
			return 0;
			break;
		default:
			break;
		}
	}
	return 1;
}

使用游标(指示器cur),这种用数组描述的链表叫静态链表

循环链表

特点:
表中最后一个结点的指针域指向头结点,整个链表形成一个环。
从表中任一结点出发均可找到表中其他结点。

图示
在这里插入图片描述

相关操作:

  • 循环链表的操作与线性链表基本一致,差别仅在于算法中的循环条件不是p或p->next是否为空,而是它们是否等于头指针。

循环链表

为了克服查找单链表的单向性的缺点(查找结点的直接前趋,需从头指针出发,执行时间O(n)),有了双向链表。

双向链表的结点中有两个指针域,一个指向直接后继,另一个指向直接前驱。

代码描述

//存储结构
typedef int ElemType;//起一个别名

 typedef struct DuLNode{
    ElemType data;
    struct DulNode *prior;
    struct DulNode *next;    
 }DuLNode, *DuLinkList;
双向链表

和单链表类似,双向链表也可以有循环表

由于链表在空间的合理利用上,和插入、删除时不需要移动等优点。在很多场合下,是线性表的事选存储结构。

插入算法实现
 //插入
Status ListInsert_DuL(DuLinkList &L,int i,ElemType e){
    //在双向链表L第i个位置之前插入元素e
    //i的合法值为1 <= i <= 表长+1
    if(!p = GetEIemP_DuL(L,i))  return ERROR;

    if(!(s = (DuLinkList)malloc(sizeof(DuLNode)))) return ERROR;
    s->data = e;
    s->prior = p->prior; p->prior->next = s;
    s->next = p; p->prior = s;
    return OK;
}//ListInsert_DuL

删除算法实现
 //删除
 Status ListDelete_DuL(DuLinkList &L,int i,EIemType &e){
    //删除双向链表L第i个位素
    //i的合法值为1 <= i <= 表长+1
    if(!p = GetEIemP_DuL(L,i))  return ERROR;

    e = p->data;
    p->prior->next = p->next;
    p->next->prior = p->prior;

    free(p);
    return OK;
 }//ListDelete_DuL

–END–

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值