算法与数据结构【数据结构】——二、线性表:链表与数组

算法与数据结构【数据结构】——二、线性表:链表与数组

线性表是最简单的线性结构;
线性结构是一个数据元素的有序集;

一、线性结构与线性表

(1)线性结构的基本特征
  • 唯一的“第一元素”
  • 唯一的“最后元素”
  • 除 最后元素 外,均有 唯一的后继
  • 除 第一元素 外,均有 唯一的前驱
(2)线性表的类型定义
数据对象:

D={ ai | ai ∈ElemSet, i=1,2,…,n, n≥0 } {称 n 为线性表的表长; 称 n=0 时的线性表为空表。}

数据关系:

R1={ <ai-1 ,ai >|ai-1 ,ai∈D, i=2,…,n } {设线性表为 (… ai …) 称 i 为 ai 在线性表中的位序。}

基本操作:

更复杂的功能 都可以基于 基本功能 实现

  • 结构初始化操作
    • InitList( &L ) 创建空表
  • 结构销毁操作
    • DestroyList( &L ) 销毁表
  • 引用型操作
    • ListEmpty( L ) 判空
    • ListLength( L ) 求表长
    • PriorElem( L, cur_e, &pre_e ) 求前驱
    • NextElem( L, cur_e, &next_e ) 求后继
    • GetElem( L, i, &e ) 索引查找——L[i]
    • LocateElem( L, e, compare( ) ) 定位函数/按值查找——if L[i]=cmp(e) return i for i in 1…n
    • ListTraverse(L, visit( )) 遍历,依次调用 visit() 函数
  • 加工型操作
    • ClearList( &L ) 置空表
    • PutElem( &L, i, &e ) 按索引修改——L[i]=e
    • ListInsert( &L, i, e ) 插入——L[i+1…n+1]=L[i…n],L[i]=e,n++
    • ListDelete(&L, i, &e)——e=L[i],L[i…n-1]=L[i+1…n],n–

二、顺序映像与数组

(1)概述
  • 逻辑关系:以 x 的存储位置和 y 的存储位置之间某种关系表示逻辑关系<x,y>,如最简单的相邻
  • 数据元素:用一组地址连续(存储位置相邻)的存储单元依次存放线性表中的数据元素
    • LOC(ai) = LOC(ai-1) + C , LOC(ai) = LOC(a1) + (i-1)×C
    • (一个元素占用C,LOC(ai)为基地址(起始地址))
(2)基本操作的代码实现
初始化——O(1)
		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;
		} 
查找——O(L)
		Int LocateElems_Sq(SqList L,ElemType e,Status(*compare)(EIemTyps,ElemType)){
			//在顺序表中查询第一个满足判定条件的数据元素,若存在则返回位序,否则返回0
			i = 1;             // i的初值为第1元素的位序
			p = L.elem;     // P的初值为第1元素的存储位置
			while (i <= L.length && !(*compare)(*p++,e))  ++i;
			if (i<=LJength)  return i;  else return0;
		}
插入——O(L)
		Status ListInsert_Sq(SqList &L, int i, ElemType e){    
			// 在L[i]前插入新元素e, i 范围为  1≤i≤L.length+1
			q = &(L.elem[i-1]);           // q指示插入位置
			for (p = &(L.elem(L.length-1]); p>=q; --p)  *(p+1) = *p;
			*q=e;     // 插入e 
			++L.length;      // 表长增I
			returnOK;
		}
删除——O(L)
		Status ListDelete_Sq(SqList &L,int i,ElemType &e){
			if ( (i<1) || (i>L.length) ) return ERROR;//删除位置不合法
			P = &(L.elem[i-l]);          // p为被删除元索的位置
			e = *p;                               // 被删除元素的值被赋给e
			q=L.eIem + L.Iength - 1;      //表尾元素的位置
			for (++p;p<=q;p++)   *(p-1)=*p;    // 被删除元索之后的元素左移
			--L.length;                    // 表长减1
			return OK;
		}
(3)数组(以及内容总结)

数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。

  • 线性表?
    • 线性表:如数组,链表、队列、栈等。线性表上数据只有 前和后 方向。
    • 非线性表:如二叉树、堆、图等。非线性表中数据之间并不是只有前后关系
  • 连续的内存空间和相同类型的数据?
    • 根据下标的“随机访问”
      • 原理——寻址公式:a[i]_address = base_address + i * data_type_size (data_type_size 表示数组中每个元素的大小)
      • 数组支持随机访问,根据下标随机访问的时间复杂度为 O(1)
    • 低效的插入删除
      • 当数据有序:搬迁原有数据——消耗资源
      • 当数据无序:插入有如下优化
      • 基于JVM的垃圾回收思想——只记录,一起删:记录下已经删除的数据,当数组没有更多空间存储时,再触发真正的删除操作。
  • 警惕数组的访问越界问题!

  • 容器能否完全替代数组?——以 JAVA中的ArrayList 为例

    • 优势:封装数组操作细节,包括插入、删除等;支持动态扩容。
    • 劣势:1. Java ArrayList 无法存储基本类型,int、long需要封装为 Integer、Long 类,但是包装类本身有性能消耗; 2. 数据大小已知,操作简单,可以直接用数组; 3. 表示多维数组时,数组更直观。—— [应用用容器没事,底层开发最好用数组]

三、链式映像与链表

(1)概述
  • 用一组地址任意的存储单元存放线性表中的数据元素。
    • 元素(数据元素的映象) + 指针(指示后继元素存储位置) = 结点 (表示数据元素 或 数据元素的映象)。
    • “结点的序列”称作链表,即用指针串联分散的内存
  • 头指针
    • 第一个数据元素的存储地址作为线性表的地址,称作线性表的头指针。
    • 有时有虚拟头结点,以指向头节点的指针为链表的头指针
(2)结点和单链表的 C 语言描述
		typedef struct LNode{         // 结点类型
			ElemType       data;    // 数据域
			struct Lnode  *next;    // 指针域
		}Lnode, *LinkList
		LinkList L;    // L为单链表的头指针
(3)单链表操作的实现
GetElem(L, i, e) 取第i个数据

移动指针,比较 j 和 i : O(L)

			Status GetEIem_L (LinkList L,int i,ElemType &e){
				//L是带头结点的链表的头指针,以e返回第i个元素
				p = L->next;  j = 1;   //p指向第一个结点,j为计数器
				while (p && j<i)  {p=p->next; ++j;  }   //顺指针向后查找,直到p指向第i个 或 p为空
				If (!p || j>i) return ERROR;    //第i个元素不存在
				e=p->data;    return OK;
			}
ListInsert(&L, i, e) 插入

找到第i-1个结点,修改其后继指针 : O(L)

			Status Listlnsert_L(LinkList L, int i,ElemType e){
				// L为带头结点的单链表的头指针,在链表第i个结点前插入元素e
				p =L;   j=0;
				while (p && j<i-1)  {  p=p->next; ++j; }     //寻找第i-1个结点
				If (!p || j>i-l)   return ERROR;                   // i大于表长或者小于1
				s  = (LinkList) malloc(sizeof(Lnode) );  // 生成新结点
				s->data = e;   s->next = p->next;    p->next=s;   // 插入
				returnOK;
			}
ListDelete(&L, i, e) 删除

找到第i-1个结点,修改其后继指针 : O(L)

			Status ListDeIete_L(LinkList L,int i,ElemType e) {
				// 删除以 L 为头指针(带头结点)的单链表中第 i 个结点
				p = L;    j = 0;
				while (p->next && j<i-1)  {p=p->next;  ++j; }    // 寻找第个结点,并令p指向其前趋
				If (!(p->next)  || j>i-1)   return ERROR;    //删除位置不合理
				q = p->next;    p -> next = q -> next;    // 删除并释放结点
				e = q -> data;  free(q);  returnOK;
			}
ClearList(&L) 重置为空表

找到头指针,依次后继置空 : O(L)

			void ClearList(&L) { //将单链表重新置为一个空表
				while (L->next){
					p=L->next;  L->next=p->next;  free(p);
				}
			}
CreateList(&L, n) 生成 n元素链表

生成链表是结点“逐个插入” : O(L)

			void CreateListL(LinkList &L,int n){   // 逆序输入n个数据元素,建立带头结点的单链表
				L=(LinkList)malloc(sizeof (Lnode) ); 
				L->next = NULL;         //先建立一个带头结点的单链表
				for (i = n; i > 0; --i) {
					p=(LinkList)malloc(sizeof(LNode));
					scanf(&p->data);                             // 输入元素值
					p->next=L->next;     L->next=p;    // 插入
				}
			}
改进链表操作的设置
  • 增加“表长”、“表尾指针” 和 “当前位置的指针” 三个数据域(时间性)
  • 将基本操作中的“位序 i ”改变为“指针 p ”(淡化位序,强化位置)
(4)特殊链表
双向链表
			typedef struct DuLNode{
				ElemType  data;//数据域
				struct DuLNode *prep;   //指向前驱的指针域
				struct DuLNode *next;    //指向后继的指针域
			}DuLNode,*DuLinkList;
  • 可以支持双向遍历,这样也带来了双向链表操作的灵活性
  • 用空间换时间的设计思想
循环链表
  • 单链表的尾结点指针指向空地址,而循环链表的尾结点指针是指向链表的头结点。
    • 判别链表中最后一个结点的条件:“后继是否为头结点”
    • 适合于:数据具有环型结构特点,如约瑟夫问题(猴子分桃问题)
  • 升级版:双向循环列表 (查找不变,插入、删除需要双方向修改)
有序表表示集合
  • 不允许出现a[i] = a[i+1]
(5)链表与数组的对比
对比项插入删除随机访问易读与预读性内存占用内存消耗
数组O(n)O(1)简单占用连续内存只存基础数据
列表O(1)O(n)复杂,无法预读只需零散内存多存一份next

和数组相比,链表更适合插入、删除操作频繁的场景,查询的时间复杂度较高。

四、应用——多项式合并

(1)算法简述
  1. 第一步:分别建立线性链表LPA、LPB、LPC的表头。
  2. 第二步:根据输入构建LPA、LPB,以储存两个多项式。在构建过程中采用插入排序的方式,保证LPA、LPB按照升序排列。
  3. 第三步:建立两个指针P、Q,分别指向LPA、LPB的表头。
  4. 第四步:为LPC申请新的结点域,加入链表末尾。当P指向域的exp等于Q指向域的exp时,新结点域的exp等于这一共同值,coef等于两者之和,P、Q指针向后移动一位;当P指向域的exp小于Q指向域的exp时,新结点域等于P指向域,P指针向后移动一位;当P指向域的exp大于Q指向域的exp时,新结点域等于Q指向域,Q指针向后移动一位。
  5. 第五步:重复第四步,直到P或者Q指向目标链表的结尾。
  6. 第六步:如果P或者Q指针存在未指向结尾的,则将其位置到结尾的所有结点域加入LPC链表末尾。
  7. 第七步:对LPC链表检查,当出现coef为零时,删除节点。
  8. 第八步:从链表头开始,遍历并输出LPC。
(2)源代码
# include <stdio.h>
# include <stdlib.h>
# define LEN(x) sizeof(x)

typedef struct node{
	double coef;	int exp;	struct node *next;
}node;

void insert(node *head,double coef,int exp){
	node *point = head;
	while ((point->next) != NULL && ((point->next)->exp)<exp) point = point->next; 
	
	if ((point->next) == NULL){
		node *newer = (node *)malloc(sizeof(LEN(node))); 
		newer->coef=coef; newer->exp=exp; 
		newer->next=NULL; point->next=newer;
	} 
	else if (((point->next)->exp) == exp) {
		point = point->next;  point->coef += coef;
	} 
	else if (((point->next)->exp) > exp) {
		node *newer = (node *)malloc(sizeof(LEN(node)));
		newer->coef=coef; newer->exp=exp; 
		newer->next=(point->next);  point->next=newer;
	}
}

void addition (node *LA, node *LB,node *LC){
	node *P=LA->next,*Q=LB->next,*T=LC; 
	
	// 开始加法
	while ( P != NULL && Q != NULL) {
		
		node *newer = (node *)malloc(sizeof(LEN(node)));
		T->next = newer; newer->next = NULL; T=T->next; 
		 
		if ( (P->exp) == (Q->exp) ){ //当P指向域的exp等于Q指向域的exp时
			newer->coef = (P->coef) + (Q->coef); newer->exp = P->exp; 
			P=P->next; Q=Q->next;  
		}
		else if ( (P->exp) < (Q->exp) ) { //当P指向域的exp小于Q指向域的exp时
			newer->coef = (P->coef); newer->exp = P->exp;
			P=P->next;
		}
		else if ( (P->exp) > (Q->exp) ) { //当P指向域的exp大于Q指向域的exp时
			newer->coef = (Q->coef); newer->exp = Q->exp;
			Q=Q->next;
		}
	}
	
	// 如果存在未指向结尾的指针,则加入所有余剩结点域
	while ( P != NULL) {
		node *newer = (node *)malloc(sizeof(LEN(node)));
		newer->coef = P->coef; newer->exp = P->exp; 
		T->next = newer; newer->next = NULL; T=T->next; 
		P = P->next;
	}
	while ( Q != NULL) {
		node *newer = (node *)malloc(sizeof(LEN(node)));
		newer->coef = Q->coef; newer->exp = Q->exp; 
		T->next = newer; newer->next = NULL; T=T->next; 
		Q = Q->next;
	}
}

void printing(node *LC){
	node *T=LC;
	int cnt=0;
	while ((T->next) != NULL) {
		while ((T->next)!=NULL && ((T->next)->coef)==0)   T->next = (T->next)->next;
		//当出现coef为零时,删除节点。
			
		T = T->next; cnt++;
		if (T==NULL) break;
		
		if ((T->exp) == 0) {
			if (cnt==1 || (T->coef)<0) printf("%lf",T->coef);
			else  printf("+%lf",(T->coef));
		}
		else if ((T->exp) == 1) {
			if (cnt==1 || (T->coef)<0) printf("%lf*X",(T->coef));
			else  printf("+%lf*X",(T->coef));
		}
		else {
			if (cnt==1 || (T->coef)<0) printf("%lf*X^%d",(T->coef),(T->exp));
			else  printf("+%lf*X^%d",(T->coef),(T->exp));
		}
	}
	printf("\n"); 
}

void main(){
	// 建立表头并初始化 
	node LPA,LPB,LPC;
	LPA.next=NULL; LPB.next=NULL; LPC.next=NULL;

	// 根据输入构建LPA、LPB,保证升序
	double coefa,coefb;
	int expa,expb;
	printf("请输入LPA,每行为coef 与 exp:\n");
	while (1){
		scanf("%lf %d",&coefa,&expa);
		if (expa<0)  break;		
		insert(&LPA, coefa, expa);
	} 
	printf("LPA表达式如下:\n");	printing(&LPA);
	
	printf("请输入LPB,每行为coef 与 exp:\n");
	while (1){
		scanf("%lf %d",&coefb,&expb);
		if (expb<0)  break;
		insert(&LPB, coefb, expb);
	} 	
	printf("LPB表达式如下:\n");	printing(&LPB);
	
	// 做加法
	addition(&LPA,&LPB,&LPC);
	
	// 输出
	printf("LPC = LPA+LPB,因此计算结果如下:\n"); 	printing(&LPC);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值