数据结构-线性表

目录

王道章节内容

知识框架

考纲内容

引入

方法1:顺序存储结构的直接表示

方法2:顺序存储结构表示非0项

方法3:链表结构存储非零项

线性表

定义

线性表的主要操作(ADT )

顺序存储结构

定义

结构代码实现

操作及实现

初始化

获得

查找

插入

删除

链式存储结构

单链表

定义

结构代码实现

操作及实现

获得

 查找

插入

删除

单链表的整表创建

头插法

尾插法

双链表

定义

结构代码实现

插入

删除

循环链表

定义

静态链表

定义

顺序表与链表的比较

参考

王道章节内容

知识框架

考纲内容

  1. 线性表的基本概念;
  2. 线性表的实现:顺序存储、链式存储;
  3. 线性表的应用。

引入

关键数据:多项式项数、各项系数及指数。

方法1:顺序存储结构的直接表示

缺点,当部分空缺时,指数过大时占据造成的空间浪费太多。

方法2:顺序存储结构表示非0项

按指数大小有序排序。

方法3:链表结构存储非零项


线性表

多项式表示问题给我们的启示:

1. 同一个问题可以有不同的表示(存储)方法;

2. 有一类共性问题:有序线性序列的组织和管理。

因此,我们对线性表给出定义。

定义

线性表的主要操作(ADT )

1、List MakeEmpty():初始化一个空线性表L;(初始化)

2、ElementType FindKth( int K, List L ):根据位序K,返回相应元素 ;(获得)

3、int Find( ElementType X, List L ):在线性表L中查找X的第一次出现位置;(查找)

4、void Insert( ElementType X, int i, List L):在位序i前插入一个新元素X;(插入)

5、void Delete( int i, List L ):删除指定位序i的元素;(删除)

6、int Length( List L ):返回线性表L的长度n。(求长度)


顺序存储结构

定义

结构代码实现

陈越实现: 

typedef struct LNode* List;
struct LNode{
	ElementType Data[MAXSIZE];	//存储元素的数组 
	int last;	//最后一个元素的位置 
};
struct LNode L;
List Ptrl;

//访问下标为 i 的元素:L.Data[i] 或 PtrL->Data[i]
//线性表的长度:L.Last+1 或 PtrL->Last+1

《大话》实现(与王道类似):

#define MAXSIZE 20          /* 存储空间初始分配量 */
typedef int ElemType;       /* ElemType类型根据实际情况而定,这里假设为int */
typedef struct
{
    ElemType data[MAXSIZE]; /* 数组,存储数据元素 */
    int length;             /* 线性表当前长度 */
}SqList;

名词解释:

PtrL 是指向(Pointer)一个线性表(Linear List)的指针;

typedef 用于创建新的类型别名,比如给已存在的数据类型起个新名字,后续用这个新名字进行定义,或者用来定义结构体,一般用法如下:

typedef existing_type new_name;
//定义新结构体

typedef struct {
    int id;
    char name[50];
} Person;

//现在你可以直接使用 Person 来声明结构体变量,而不需要使用 struct 关键字:

Person p1;
p1.id = 1;
strcpy(p1.name, "Alice");

struct 用于定义结构体类型。结构体是一种用户自定义的数据类型,它允许你组合不同类型的变量来组成一个单一的复合数据类型。结构体可以包含各种类型的数据成员,如整数、浮点数、字符、数组、甚至其他的结构体等,一般用法如下:

struct struct_name {
    type1 member1;
    type2 member2;
    ...
    typen membern;
} variable_name;
struct Student {
    int id;
    char name[50];
    float gpa;
};

// 创建一个结构体变量
struct Student s1;
s1.id = 1;
strcpy(s1.name, "Alice");
s1.gpa = 3.8;

从以上我们可以发现,描述顺序存储结构需要“三个属性”

  • 存储空间的起始位置:数组data,它的存储位置就是存储空间的位置;
  • 线性表的最大存储容量:MAXSIZE;
  • 线性表的当前长度:length,它和数组的长度不一样,它是变化的,全取决于表内数据元素个数。

操作及实现

初始化

List MakeEmpty( )
{ List PtrL;
 PtrL = (List )malloc( sizeof(struct LNode) );
 PtrL->Last = -1; 
 return PtrL;
}

获得

按序号查找,返回第i个数据元素的值

《大话》实现:

#define OK 1
#define ERROR 0
typedef int Status;         /* Status是函数的类型,其值是函数结果状态代码,如OK等 */

/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:用e返回L中第i个数据元素的值,注意i是指位置,第1个位置的数组是从0开始 */
Status GetElem(SqList L,int i,ElemType *e)
{
    if(L.length==0 || i<1 || i>L.length)
            return ERROR;
    *e=L.data[i-1];

    return OK;
}

查找

按值查找,返回对应值的序号

陈越实现:

int Find( ElementType X, List PtrL )
{ int i = 0;
 while( i <= PtrL->Last && PtrL->Data[i]!= X )
 i++;
 if (i > PtrL->Last) return -1; /* 如果没找到,返回-1 */
 else return i; /* 找到后返回的是存储位置 */ 
}

《大话》实现:

/* 初始条件:顺序线性表L已存在 */
/* 操作结果:返回L中第1个与e满足关系的数据元素的位序。 */
/* 若这样的数据元素不存在,则返回值为0 */
int LocateElem(SqList L,ElemType e)
{
    int i;
    if (L.length==0)
            return 0;
    for(i=0;i<L.length;i++)
    {
            if (L.data[i]==e)
                    break;
    }
    if(i>=L.length)
            return 0;

    return i+1;
}

插入

第 i (1≤i≤n+1)个位置上插入一个值为X的新元素:

插入算法的思路:

  1. 如果插入位置不合适,抛出异常;
  2. 如果线性表长度大于等于数组长度,则抛出异常或动态增加容量;
  3. 从最后一个元素开始向前遍历到第i个位置,分别将它们向后移动一个位置;
  4. 将要插入元素填入位置i处;
  5. 表长+1。

陈越实现:

void Insert( ElementType X, int i, List PtrL )
{ int j;
 if ( PtrL->Last == MAXSIZE-1 ){ /* 表空间已满,不能插入*/
 printf("表满"); 
 return; 
 } 
 if ( i < 1 || i > PtrL->Last+2) { /*检查插入位置的合法性*/
 printf("位置不合法");
 return; 
 } 
 for ( j = PtrL->Last; j >= i-1; j-- )
 PtrL->Data[j+1] = PtrL->Data[j]; /*将 ai~ an倒序向后移动*/
 PtrL->Data[i-1] = X; /*新元素插入*/
 PtrL->Last++; /*Last仍指向最后元素*/
 return; 
} 

《大话实现》:

/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L), */
/* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */
Status ListInsert(SqList *L,int i,ElemType e)
{ 
	int k;
	if (L->length==MAXSIZE)  /* 顺序线性表已经满 */
		return ERROR;
	if (i<1 || i>L->length+1)/* 当i比第一位置小或者比最后一位置后一位置还要大时 */
		return ERROR;

	if (i<=L->length)        /* 若插入数据位置不在表尾 */
	{
		for(k=L->length-1;k>=i-1;k--)  /* 将要插入位置之后的数据元素向后移动一位 */
			L->data[k+1]=L->data[k];
	}
	L->data[i-1]=e;          /* 将新元素插入 */
	L->length++;

	return OK;
}

删除

删除表的第 i (1≤i≤n)个位置上的元素:

删除算法的思路:

  1. 如果删除的位置不合适,抛出异常;
  2. 取出删除元素;
  3. 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置;
  4. 表长-1。

 陈越实现:

void Delete( int i, List PtrL )
{ int j;
 if( i < 1 || i > PtrL->Last+1 ) { /*检查空表及删除位置的合法性*/
 printf (“不存在第%d个元素”, i ); 
 return ; 
 }
 for ( j = i; j <= PtrL->Last; j++ )
 PtrL->Data[j-1] = PtrL->Data[j]; /*将 ai+1~ an顺序向前移动*/
 PtrL->Last--; /*Last仍指向最后元素*/
 return; 
}

《大话》实现:

/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */
Status ListDelete(SqList *L,int i,ElemType *e) 
{ 
    int k;
    if (L->length==0)               /* 线性表为空 */
		return ERROR;
    if (i<1 || i>L->length)         /* 删除位置不正确 */
        return ERROR;
    *e=L->data[i-1];
    if (i<L->length)                /* 如果删除不是最后位置 */
    {
        for(k=i;k<L->length;k++)/* 将删除位置后继元素前移 */
			L->data[k-1]=L->data[k];
    }
    L->length--;
    return OK;
}

链式存储结构

单链表

定义

由以上内容可知,顺序存储结构最大的缺点是插入和删除需要移动大量元素,因此,链式结构应运而生。

链表结构 (Linked List)

链表是由一系列节点组成的集合,其中每个节点包含两部分:

  • 数据域(Data Field):存储实际的数据值。
  • 指针域(Pointer Field):存储指向下一个节点的地址。

链表的第一个节点通常称为头节点(Head),最后一个节点的指针域为空或指向一个特殊的值,表示链表的结束。链表可以是单向的(单链表),也可以是双向的(双链表),在双向链表中,每个节点除了有指向下一个节点的指针外,还有一个指向前一个节点的指针。

结点(Node)

结点是链表的基本组成单位,每个结点都包含数据域和指针域。数据域用于存储用户需要保存的信息,而指针域则用于链接到链表中的下一个结点,从而形成一条链。

指针域(Pointer Field)

指针域是结点的一个组成部分,它存储了下一个结点在内存中的地址。在双链表中,除了指向下一个结点的指针,还会有一个指向前一个结点的指针。指针域允许链表中的结点相互连接,从而形成一个线性的结构。

  • 头指针:链表中第一个结点的存储位置;
  • Last结点指针:NULL或^代表“空”。

链表的灵活性在于它可以动态地添加或删除结点,而不需要重新分配整个数据结构的空间,这与数组在插入或删除元素时可能需要移动大量元素以保持连续性不同。

补充:头结点与头指针

结构代码实现

陈越实现:

typedef struct LNode *List;
struct LNode{ 
 ElementType Data; 
 List Next;
}; 
struct Lnode L;
List PtrL;

《大话》实现:

//线性表的单链表存储结构
typedef struct Node
{
    ElemType data;
    struct Node *next;
}Node;
typedef struct Node *LinkList; /* 定义LinkList */

操作及实现

获得

按序号查找,返回第i个数据元素的值

获得算法思路:

  1. 声明一个指针p指向链表第一个结点,初始化从j从1开始;
  2. 当 j<i 时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1;
  3. 若到链表末尾p为空,这说明第i]个结点不存在;
  4. 否则查找成功,返回结点p的数据。

陈越实现:

List FindKth( int K, List PtrL )
{ List p = PtrL;
 int i = 1;
 while (p !=NULL && i < K ){
 p = p->Next;
 i++; 
 }
 if ( i == K ) return p; 
 /* 找到第K个,返回指针 */
 else return NULL; 
 /* 否则返回空 */
}

《大话》实现:

/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:用e返回L中第i个数据元素的值 */
Status GetElem(LinkList L,int i,ElemType *e)
{
	int j;
	LinkList p;		/* 声明一结点p */
	p = L->next;		/* 让p指向链表L的第一个结点 */
	j = 1;		/*  j为计数器 */
	while (p && j<i)  /* p不为空或者计数器j还没有等于i时,循环继续 */
	{   
		p = p->next;  /* 让p指向下一个结点 */
		++j;
	}
	if ( !p || j>i ) 
		return ERROR;  /*  第i个元素不存在 */
	*e = p->data;   /*  取第i个元素的数据 */
	return OK;
}

名词解释:

后缀自增 (i++):

  • 在使用 i++ 时,首先使用 i 的原始值,然后才将 i 的值加 1。
  • 如果 i++ 出现在赋值语句的右侧,那么 i 的原始值会被用于赋值,之后 i 的值才会增加。

前缀自增 (++i):

  • 在使用 ++i 时,先将 i 的值加 1,然后再使用新的值。
  • 如果 ++i 出现在赋值语句的右侧,那么 i 的新值会被用于赋值。
 查找

按值查找,返回对应值的序号

陈越实现:

List Find( ElementType X, List PtrL )
{ 
 List p = PtrL;
 while ( p!=NULL && p->Data != X )
 p = p->Next; 
 return p;
} 

《大话》实现:

/* 初始条件:链式线性表L已存在 */
/* 操作结果:返回L中第1个与e满足关系的数据元素的位序。 */
/* 若这样的数据元素不存在,则返回值为0 */
int LocateElem(LinkList L,ElemType e)
{
    int i=0;
    LinkList p=L->next;
    while(p)
    {
        i++;
        if(p->data==e) /* 找到这样的数据元素 */
                return i;
        p=p->next;
    }

    return 0;
}
插入

在第  i-1 (1 ≤ i ≤ n+1) 个结点后插入一个值为X的新结点

注意先后顺序!!!可以把这个操作近似一种缝补、接线操作。

插入前:

插入后:

插入算法的思路:

  1. 声明一指针 p 指向链表头结点,初始化 j 从1开始;
  2. 当 j<i 时,就遍历链表,让 p 的指针向后移动,不断指向下一结点;
  3. 若到链表末尾 p 为空,则说明第 i 个结点不存在;
  4. 否则查找成功,在系统生成一个空节点 s;
  5. 将数据元素 e 赋值给 s->data;
  6. 单链表的插入标准语句 s->next = p->next;  p->next = s;
  7. 返回成功。

陈越实现:

List Insert( ElementType X, int i, List PtrL )
{ List p, s;
 if ( i == 1 ) { /* 新结点插入在表头 */
 s = (List)malloc(sizeof(struct LNode)); /*申请、填装结点*/
 s->Data = X; 
 s->Next = PtrL;
 return s; /*返回新表头指针*/
 }
 p = FindKth( i-1, PtrL ); /* 查找第i-1个结点 */
 if ( p == NULL ) { /* 第i-1个不存在,不能插入 */
 printf("参数i错");
 return NULL; 
 }else {
 s = (List)malloc(sizeof(struct LNode)); /*申请、填装结点*/
 s->Data = X; 
 s->Next = p->Next; /*新结点插入在第i-1个结点的后面*/
 p->Next = s; 
 return PtrL;
 }

《大话》实现:

/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L), */
/* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */
Status ListInsert(LinkList *L,int i,ElemType e)
{ 
	int j;
	LinkList p,s;
	p = *L;   
	j = 1;
	while (p && j < i)     /* 寻找第i个结点 */
	{
		p = p->next;
		++j;
	} 
	if (!p || j > i) 
		return ERROR;   /* 第i个元素不存在 */
	s = (LinkList)malloc(sizeof(Node));  /*  生成新结点(C语言标准函数) */
	s->data = e;  
	s->next = p->next;      /* 将p的后继结点赋值给s的后继  */
	p->next = s;          /* 将s赋值给p的后继 */
	return OK;
}

名词解释:

malloc(memory allocation

即动态分配内存。malloc 函数允许你在程序运行时请求一定大小的内存块,这对于动态数据结构(如链表、树等)非常重要,因为它们的大小可能在程序运行过程中发生变化。一般用法如下:

void *malloc(size_t size);
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p;  
//这里的星号 * 表示 p 是一个指针变量,它可以存储一个整数类型的地址。
//int *p; 的意思是 p 是一个指向 int 类型数据的指针。

    p = (int *)malloc(sizeof(int)); // 分配足够的内存来存储一个整数
//这里的星号 * 在 malloc 函数调用的结果后面,用于类型转换。
//(int *) 是类型转换操作,将 malloc 返回的 void * 类型转换为 int * 类型,
//使得 p 可以安全地指向这块内存。

    if (p == NULL) {
        printf("内存分配失败。\n");
        exit(1); // 如果内存分配失败,终止程序
    }

    *p = 42; // 给分配的内存赋值
    printf("分配的内存中存储的值是: %d\n", *p);

    free(p); // 释放分配的内存
    return 0;
}
删除

删除链表的第 i (1≤i≤n)个位置上的结点

删除算法的思路:

  1. 声明一指针 p 指向链表头结点,初始化 j 从1开始;
  2. 当 j < i 时,就遍历链表,让 p 的指针向后移动,不断指向下一个结点,j 累加1;
  3. 若到链表末尾 p 为空,则说明第 i 个结点不存在;
  4. 否则查找成功,将欲删除的结点 p->next 赋值给 q;
  5. 单链表的删除标准语句 p->next = q->next;
  6. 将 q 结点中的数据赋值给 e,作为返回;
  7. 释放 q 结点;
  8. 返回成功。

        ·   
  陈越实现:

List Delete( int i, List PtrL )
{ List p, s;
 if ( i == 1 ) { /* 若要删除的是表的第一个结点 */
 s = PtrL; /*s指向第1个结点*/
 if (PtrL!=NULL) PtrL = PtrL->Next; /*从链表中删除*/
 else return NULL;
 free(s); /*释放被删除结点 */
 return PtrL;
 }
 p = FindKth( i-1, PtrL ); /*查找第i-1个结点*/
 if ( p == NULL ) { 
 printf(“第%d个结点不存在”, i-1); return NULL; 
 } else if ( p->Next == NULL ){ 
 printf(“第%d个结点不存在”, i); return NULL; 
 } else {
 s = p->Next; /*s指向第i个结点*/
 p->Next = s->Next; /*从链表中删除*/
 free(s); /*释放被删除结点 */
 return PtrL;
 }

 《大话》实现:

/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */
Status ListDelete(LinkList *L,int i,ElemType *e) 
{ 
	int j;
	LinkList p,q;
	p = *L;
	j = 1;
	while (p->next && j < i)	/* 遍历寻找第i个元素 */
	{
        p = p->next;
        ++j;
	}
	if (!(p->next) || j > i) 
	    return ERROR;           /* 第i个元素不存在 */
	q = p->next;
	p->next = q->next;			/* 将q的后继赋值给p的后继 */
	*e = q->data;               /* 将q结点中的数据给e */
	free(q);                    /* 让系统回收此结点,释放内存 */
	return OK;
}

单链表的整表创建

头插法

始终让新结点在第一的位置

实际上就是插入的操作,但输入与次序逆置

《大话》实现:

/*  随机产生n个元素的值,建立带表头结点的单链线性表L(头插法) */
void CreateListHead(LinkList *L, int n) 
{
	LinkList p;
	int i;
	srand(time(0));                         /* 初始化随机数种子 */
	*L = (LinkList)malloc(sizeof(Node));
	(*L)->next = NULL;                      /*  先建立一个带头结点的单链表 */
	for (i=0; i<n; i++) 
	{
		p = (LinkList)malloc(sizeof(Node)); /*  生成新结点 */
		p->data = rand()%100+1;             /*  随机生成100以内的数字 */
		p->next = (*L)->next;    
		(*L)->next = p;						/*  插入到表头 */
	}
}
尾插法

始终插在表尾

/*  随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法) */
void CreateListTail(LinkList *L, int n) 
{
	LinkList p,r;
	int i;
	srand(time(0));                      /* 初始化随机数种子 */
	*L = (LinkList)malloc(sizeof(Node)); /* L为整个线性表 */
	r=*L;                                /* r为指向尾部的结点 */
	for (i=0; i<n; i++) 
	{
		p = (Node *)malloc(sizeof(Node)); /*  生成新结点 */
		p->data = rand()%100+1;           /*  随机生成100以内的数字 */
		r->next=p;                        /* 将表尾终端结点的指针指向新结点 */
		r = p;                            /* 将当前的新结点定义为表尾终端结点 */
	}
	r->next = NULL;                       /* 表示当前链表结束 */
}

双链表

定义

双链表结点中有两个指针 prior 和 next,分别指向其直接前驱和直接后继。所以,头结点的 prior 和尾结点的 next 都是NULL。

结构代码实现

王道实现:

typedef struct DNode{            //定义双链表结点类型
    ElemType data;               //数据域
    struct DNode *prior, *next;  //前驱和后续指针
}DNode, *DLinklist;

插入

删除

循环链表

定义

静态链表

定义

《大话》实现(与王道类似):

/* 线性表的静态链表存储结构 */
typedef struct 
{
    ElemType data;
    int cur;  /* 游标(Cursor) ,为0时表示无指向 */
} Component,StaticLinkList[MAXSIZE];

顺序表与链表的比较

参考

王道《数据结构》;

《大话数据结构》;

陈越《数据结构》。

  • 28
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
线性表是一种常见的数据结构,它表示具有相同数据类型的一组元素的有序序列。线性表中的元素之间存在一种顺序关系,每个元素都有一个前驱和一个后继(除了第一个元素没有前驱,最后一个元素没有后继)。线性表可以用顺序存储结构或链式存储结构实现。 在顺序存储结构中,线性表的元素按照顺序存储在连续的内存空间中,可以通过元素的下标来访问和操作元素。插入或删除元素时,需要移动其他元素,因此操作的时间复杂度较高。 链式存储结构中,线性表的每个元素都包含一个数据域和一个指针域,指针指向下一个元素。通过指针的链接,元素可以按照任意顺序存储在内存中,插入和删除操作只需要改变指针的指向,因此时间复杂度较低。 线性表常见的操作包括插入、删除、查找、获取长度等。其中插入和删除操作需要注意保持线性表的顺序关系。 常见的线性表有数组、链表、栈和队列。数组是最简单的线性表,通过下标可以直接访问元素;链表是动态存储结构,插入和删除操作方便,但访问元素需要遍历链表;栈是一种特殊的线性表,只允许在表的一端进行插入和删除操作;队列也是一种特殊的线性表,只允许在表的一端进行插入操作,在另一端进行删除操作。这些数据结构在实际应用中都有各自的应用场景和优缺点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值