线性表

1 线性表的定义与操作

1.1 定义

线性表是具有相同数据类型的n(n>=0)个数据元素的有限序列,其中n为表长,当n=0时,线性表是一个空表。若用L来命名线性表,则一般表示为:
在这里插入图片描述其中:

  1. ai是线性表中的“第i个”元素,i代表的是线性表中的位序
  2. a1是表头元素、an是表尾元素
  3. 除了表头元素外,每个元素有且只有一个直接前驱;除了表尾元素,每个元素有且只有一个直接后继

在这里插入图片描述

1.2 线性表的基本操作

  1. 线性表的基本操作无非就是“创销,增删改查”:创建or销毁线性表、增加or删除or修改or查找线性表中的元素
  2. 以下都是伪代码,简单写各个功能的函数名以及具体实现的功能
  • InitList(&L):初始化表。创建一个空的线性表,分配内存空间
  • DestroyList(&L):销毁操作。销毁线性表,并释放线性表所占用的内存空间。
  • ListInsert(&L,i,e):插入(增加元素)操作。在表L的第i个位置插入指定元素e.
  • ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
  • LocateElem(L,e):按值查找操作。在表L中查找给定关键字值的元素。
  • GetElem(L,i):按位查找操作。获取表L第i个位置的元素的值
  • Length(L):求表长。返回线性表L的长度,即线性表L中数据元素的个数。
  • PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值。
  • Empty(L):判空操作。若L为空表,则返回true,否则返回false.

2 顺序表(顺式存储)

2.1 顺序表的定义

  • 线性表的顺式存储也叫顺序表。
  • 顺式存储:把逻辑上相邻的元素存储在物理位置也相邻的存储单元中,元素之间的关系也由存储单元的邻接关系来体现
  • 为了使逻辑上相邻的元素,在物理上也相邻,顺式表用一组地址连续的存储单元依次存储线性表中的数据元素

2.2 代码实现顺序表的定义

实现顺序表的方式既可以是静态的也可以是动态的。静态分配指的是线性表的大小和空间已经确定好,一旦空间占满,再加入新数据会导致溢出,进而程序崩溃;动态分配则是指存储空间是在程序执行期间通过动态存储分配语句分配的,一旦空间占满,就另外开辟一块更大的存储空间,用以替换原来的存储空间。

静态分配
#define MaxSize 10
typdef struct{
	ElemType data[MaxSize];
	int length;
	}SqList1;
------------------------------------------------------	----------------------------------------------------------
动态分配
typdef struct{
	ElemType *data;
	int length,MaxSize;
	}SqList2;
	// C的初始动态分配语句
	L.data = (ElemType *)malloc(sizeof(ElemType)*InitSize);
	// C++的初始动态分配语句
	L.data = new ElemType[InitSize];

2.3 顺序表特点

  • 随机访问,仅仅通过表头元素地址以及元素序号就可以在O(1)内找到指定元素
  • 存储密度高,每个结点只存储数据元素
  • 插入和删除操作需要移动大量元素,由于逻辑上相邻的元素物理上也相邻

2.4 顺序表基本操作的实现

  • 插入操作

在这里插入图片描述

bool ListInsert(SqList &L,int i,ElemType 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];
	}
	data[i-1] = e;// 将e赋值给第i个位置(数组里对应i-1)
	L.length++;
	return true;
}
  • 删除操作

在这里插入图片描述

bool ListDelete(Sqlist &L,i,ElemType &e){
	if(i<1 || i>L.length+1)//删除位置不合法,不可以删除不存在的位置
		return false;
	e = L.data[i-1];// 保存要删除的元素
	for(int j = i; j <= L.length-1; j++ ){// 从要删除的元素的下一个位置开始到表尾元素依次向前移动一个位置
		L.data[j-1] = L.data[j];
	}
	L.length--;
	return true;
}
  • 按值查找
int LocateElem(SqList L,ElemType e){
	int i;
	for(i = 0; i < L.length; i++){// 遍历顺序表寻找第一个值==e的元素
		if(L.data[i] == e) {
			return i+1;// 位序 == 下标 + 1
		}
	}
	return 0;
}
  • 按位查找
int GetElem(SqList L,int i) {
	if(i < 1 || i > MaxSize){
		return 0;
	}
	return L.data[i-1];
}

3 链表(链式存储)

3.1 单链表

3.1.1 单链表的定义

线性表的链式存储叫做单链表,指通过一组任意的存储单元来存储线性表中的数据元素。
在这里插入图片描述与顺序表相比,单链表的优点是不用占用大片连续的空间,比较灵活,同时扩展新空间也比较方便;但是由于为了建立数据元素之间的线性关系,链表的每个结点除了要存储自身信息以外还要存储一个指向其后继的指针,这样的话,存储密度低同时链表就不支持随机存取了,需要通过指针一个一个的去查找。
代码描述如下:

typedef struct LNode {
	ElemType data;
	struct LNode *next;
}LNode, *LinkList;
3.1.2 头结点的引用

为了操作方便,我们在单链表第一个结点之前附加一个结点,该节点不设置任何信息,它的指针域指向线性表的第一个元素,该节点称为头节点。
这样设置怎就方便了呢?

  • 有了头节点,我们的第一个数据结点就有了指针指向了,所以这样在结点上的操作达到了一致;
  • 头指针永远指向链表的第一个结点,因此当链表为空时,操作也达成了统一,如下:
L->next == null;// 有头节点
L == null;// 没有附加头节点
3.1.3 单链表的基本操作实现
  1. 头插法创建单链表:新建立的结点放在头节点之后
LinkList List_HeadInsert(LinkList &L) {
	LNode *s;
	int x;
	// 初始化头结点
	L = (LinkList)malloc(sizeof(LNode));
	L->next = 0;
	// 输入数字 为9999时 终止输入
	scanf("%d",&x);
	while(x != 9999) {
		// 初始化新结点
		s = (LinkList)malloc(sizeof(LNode));
		s->data = x;
		s->next = L->next;// 头结点指向的位置赋值给新结点的指针,头结点的指针域信息不可以丢失,否则新节点就链接不上其他的结点了
		L->next = s;// 再将头结点的指针指向新结点
		scanf("%d",&x);
	}
	return L;
}
  1. 尾插法创建单链表:新建立的结点放在表尾结点之后,优点是输入次序和结点次序一致
LinkList List_TailInsert(LinkList &L) {
	int x;
	L = (LinkList)malloc(sizeof(LNode));
	LNode *s,*r = L;// s指向新结点、r指向尾结点(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;
}
  1. 按位查找:从第一个结点开始依次寻找,直到找到第i个结点结束,否则返回最后一个结点指针域NULL
LNode *GetElem(LinkList L,int i) {
	if(i == 0) {// 返回头结点
		return L;
	}
	if(i < 0) { // 输入不合法
	return L;
	}
	int j = 1; // 计数
	LNode *p = L->next; // 初始化移动指针
	while(p && j < i) {// 从第一个结点开始寻找,查找第i个结点
		p = p->next;
		j++;
	}
	return p;
}
  1. 按值查找:从第一个结点开始寻找,直到找到第i个结点结束,否则返回最后一个结点指针域NULL
LNode *LocateElem(LinkList L,ElemType e) {
	LNode *p = L->next;
	while(p != null&&p->data != e) {
		p = p->next;
	}
	return p;
}
  1. 插入操作:
// 按照位序插入:先检查i的合法性,再找到第i个的**前驱结点**,最后进行插入
bool ListInsert(LinkList &L,int i,ElemType e) {
	if(i < 1) {
		return false;// i不合法
  }
  LNode *p = L->next;
  int j = 0;
  while(p != null&&j < i-1) {// 找到第i个结点的前驱节点
		p = p->next;
	}
	if(p == null) {
		return false;// i不合法
	}
	// 创建新节点
	LNode *s = (LNode*)malloc(sizeof(LNode));
	s->data = e;
	// 将新结点插入在第i-1个结点后面
  s->next = p->next;
  p->next = s;
  return true;
}

在这里插入图片描述

// 已知道待插入结点和插入位置
bool InsertPriorNode(LNode *p,LNode *s) {
	if(p == Null || s == Null)
		return false;
		// 待插入结点连接到链表
		s->next = p->next;
		p=>next = s;
		// 偷梁换柱,将插入位置的结点里面的data与待插入的data交换
		ElemType temp = s->data;
		s->data = p->data;
		p->data = temp;
		return true;
}
  1. 删除操作
// 按位序删除:检查删除结点合法性,找到被删除元素的前驱结点,将其删除
bool ListDelete(LinkList &L, int i, ElemType &e) {
	if(i<1)
		return false;// 删除位置不合法
		LNode *p;// 移动指针
		int j = 0;// 表示指针p指向第几个结点
		p = L;
		while(p != null && j<i-1) {// 找到前驱结点
			p = p->next;
			j++;
	}
	if(p==Null)
		return false;// 删除位置不合法
	LNode *q = p->next;// q指向被删除结点
	e = q->data;// 保存即将被删除的数据
	p->next = q->next;// 将前驱节点与即将删除的结点的下一个结点链接
	free(q);// 删除
	return true;
}
// 指定结点删除
bool DeleteNode(LNode *P) {
	if(p == null)
		return false;
	LNode *q = p->next; // q指针指向p的后继结点
	p->data = p->next->data;// 和后继结点交换数据域:当p为表尾结点时出现bug
	p->next = q->next;// 将*q结点从链中释放
	free(q);
	return true;
}
  1. 求表长:遍历一遍链表
int length(LinkList L) {
	if(L->next == null) {
		return 0;// 空表
	}
	LNode* p = L->next;
	int j = 1;// 表长不包括头结点
	while(p!=null) {
		j++;
	}
	return j;
}

3.2 双链表

3.2.1 双链表的定义

单链表只能向后遍历,而双链表可以双向遍历,为了支持双向遍历,双链表拥有两个指针分别指向其前驱与后继结点,代码实现如下:

typedef struct DNode {
	ElemType data;
	struct DNode *prior,*next;
}DNode, *DLinkList;
3.2.2 双链表的操作实现
  • 初始化
    在这里插入图片描述
bool InitDLinkList(DLinkList &L){
	L =(DNode*)malloc(sizeof(DNode));// 分配头结点
	if(L==NULL)	// 内存不足,分配失败
		return false;
	L->prior = null;// 头结点的prior永远指向null
	L->next = null;// 头结点之后暂时没有结点
	return true;
}
  • 插入
    在这里插入图片描述
bool InsertNextDNode(DNode *p,DNode *s) {
  if(p == null || s == null) {// 非法参数
		return false;
  }
	s->next = p->next;
	if(p->next != null) {// 如果p结点有后继结点
		p->next->prior = s;
	}
	s->prior = p;
	p->next = s;
	return true;
}
  • 删除
    在这里插入图片描述
// 删除p结点的后继结点
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;
	}
	free(q);
	return true;
}
  • 遍历
    在这里插入图片描述

3.3 循环链表

3.3.1 循环单链表
  1. 定义:相比单链表,循环单链表的表尾元素的指针指向头结点而不是Null,从而使整个链表变为一个环
    在这里插入图片描述

  2. 定义

typedef struct LNode {
	ElemType data;
	struct LNode *next;
}LNode,*LinkList;
  1. 初始化
bool InitList(LinkList &L) {
	L = (LNode*) malloc(sizeof(LNode));
	if(L == Null) {// 分配内存失败
		return false;  
	}
	L->next = L;// 初始状态:头结点的指针指向自己
	return true;
}
  1. 判断循环单链表是否为空
bool Empty(LinkList L) {
	if(L->next == L) {
		return true;
	}else {
		return false;
	}
}
  1. 判断结点p是否为表尾结点
bool isTail(LinkList L,LNode *p) {
	if(p->next == L)// 表尾元素的指针指向头结点
		return true;
	else 
		return false;
}
  1. 插入
// 按照位序插入:先检查i的合法性,再找到第i个的**前驱结点**,最后进行插入
bool ListInsert(LinkList &L, int i, ElemType e) {
    if (L == Null || i < 0 || i > L.length+1) return false;  // 本身为空链表表或如果插入位置超出链表范围,返回 false

    LNode *s = (LNode*)malloc(sizeof(LNode));  // 创建新节点
    s->data = e;
    
    LNode *p = L;// 移动指针
    int j = 0;// 记录移动指针p经过第几个结点
    
    LNode *q = L;// 初始化尾指针
    while (q->next != L) {  // 找到尾节点 q
        q = q->next;
    }

    if (i == 0) {  // 插入到链表头部
        q->next = s;
        s->next = p;
    } else if (i == L.length+1) {  // 插入到链表末尾
        s->next = p->next;
        p->next = s;
    } else {  // 插入到其他位置
        while (j != i-1) {  // 找到第 i-1 个节点
            p = p->next;
            j++;
        }
        s->next = p->next;
        p->next = s;
    }
    return true;
}
  1. 删除
bool ListDelete(LinkList &L, int i) {
    if (i < 0 || i >= L->length)
        return false;

    LNode *p = L;      // p 指向待删除节点的前驱节点
    int j = 0;
    while (j < i) {    // 找到待删除位置的前一个节点
        p = p->next;
        j++;
    }

    LNode *q = p->next;   // 待删除节点

    if (q == L->next)     // 如果待删除节点是头节点
        L->next = q->next;

    if (q == L)           // 如果待删除节点是尾节点
        L = p;

    p->next = q->next;    // 将待删除节点的前驱节点指向待删除节点的后继节点

    if (L->next == L)     // 如果链表中只有一个节点
        L->next = nullptr;

    free(q);              // 释放待删除节点的内存
    L->length--;          // 更新链表长度
    return true;
}
3.3.2 循环双链表
  1. 定义:相比双链表,循环双链表的头结点的prior指向表尾结点而表尾结点的next指向头结点
    在这里插入图片描述
  2. 初始化
bool InitDLinkList(DLinkList &L) {
	L = (DNode *)malloc(sizeof(DNode);
	if(L == Null)
		return false;
	L->prior = L;
	L->next = L;
	return true;
}
  1. 判断循环双链表是否为空
bool Empty(DLinkList L) {
	if(L->next == L) {
		return true;
	}else {
		return false;
	}
}
  1. 判断结点是否为表尾结点
bool isTail(DLinkList L,LNode *p) {
	if(p->next == L)// 表尾元素的指针指向头结点
		return true;
	else 
		return false;
}
  1. 插入
  2. 删除

3.4 静态链表

3.4.1 什么是静态链表

静态链表借助数组来表述线性表的链式存储结构,需要提前分配一批连续的内存空间,这里的指针指的是结点的相对地址(数组下标),又称游标。游标为-1时表示已经到达表尾。
在这里插入图片描述

3.4.2 如何定义静态链表
#define Maxsize 50
typdef Node {
 	ElemType data;
 	int next;// 下个数组的下标
 	};
 	typedef struct Node SlinkList{MaxSize};
3.4.1 基本操作的实现
  1. 查找
int LocateElem(SLinkList L, ElemType elem) {
    int index = L.r[0].next;  // 从第一个节点开始查找

    while (index && L.r[index].data != elem) {
        index = L.r[index].next;
    }

    return index;  // 返回查找到的节点下标,如果未找到则返回0
}

  1. 插入:找到一个空的结点,存入数据元素;从头结点出发找到位序为i-1的结点;修改新节点的next;修改i-1号结点的next;
void ListInsert(SLinkList *L, int i, ElemType elem) {
    if (i < 1 || i > L->length + 1) {  // 越界检查
        printf("插入位置不合法\n");
        return;
    }

    if (L->length >= MaxSize - 1) {  // 静态链表已满
        printf("静态链表已满,无法插入\n");
        return;
    }

    int index = 0;
    for (int j = 1; j <= L->length; j++) {
    		
        if (j == i) {
            // 在第i个位置插入新节点
            index = j;
            break;
        }
        if (L->r[j].next == 0) {
            // 在链表末尾插入新节点
            index = L->length + 1;
            break;
        }
    }

    if (index != 0) {
        // 将新节点插入链表
        L->r[index].data = elem;
        L->r[index].next = L->r[0].next;
        L->r[0].next = index;
        L->length++;
        printf("成功在第%d个位置插入新节点,元素值为%d\n", index, elem);
    } else {
        printf("插入失败\n");
    }
}
  1. 删除:从头结点出发找到前驱结点;修改前驱节点的游标;被删除的结点next设为-2;
void ListDelete(SLinkList *L, int i) {
    if (i < 1 || i > L->length) {  // 越界检查
        printf("删除位置不合法\n");
        return;
    }

    int index = (i == 1) ? L->r[0].next : 1;
    int preIndex = 0;

    for (int j = 1; j <= i; j++) {
        if (j == i) {
            // 找到待删除节点,调整连接关系
            L->r[preIndex].next = L->r[index].next;
            L->length--;
            printf("成功删除第%d个位置的节点,元素值为%d\n", i, L->r[index].data);
            return;
        }

        if (index == 0) {
            // 链表遍历结束仍未找到待删除位置,删除失败
            printf("删除失败\n");
            return;
        }

        preIndex = index;
        index = L->r[index].next;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值