数据结构基础——线性结构

是本人对浙大数据结构课程的学习过程笔记,非常口语化不专业。

数据结构与算法引言

数据的物理结构主要有顺序存储结构(通常为数组)和链式存储结构(通常为链表),其次还有索引结构和散列结构等,由物理结构做基础,构成更多样的逻辑结构。
当一个算法的时间复杂度为O(n)时,就要想办法优化成对数复杂度O(logN)。
结点:一个数据存储单元,通常包含一个数据域和若干个指向其他结点的指针域。
几乎可以说,任何特定的数据结构都是为了实现某种特定的算法。特定的算法往往搭配特定的数据结构,算法与数据结构相辅相成。
在STL中常见的容器(数据结构):数组(array),链表(list),树(tree),栈(stack),队列(queue),集合(set),映射表(map)。

链表

链表是一种存储结构。链表的第一个结点称为头结点,同时自身的地址为链表的头指针Head。

头结点可以是不存储数据的空结点(或用于存储链表长度),只指向下一个结点,头结点的下一个结点才正式使用,称为首结点,最后一个指向NULL的结点称为尾结点。空的头结点不是必要的,非空头结点的链表在后续操作时需要考虑出现空链表的特殊情况,有头结点则可以方便后续统一操作,操作完头结点可以删除或不考虑,直接取用下一个结点。

对一个单链表而言,在头结点处插入和删除结点都可以方便地找到地址去操作,但尾结点仅可插入结点,删除结点时由于找不到上一个结点的地址而难以进行。因此尽管在构造动态链表时尾插法依然可用,但通常优先使用头插法,包括其他类似操作,在后文的链式结构会有体现。

操作链表时,为避免丢失地址,要让新结点先有所指向,然后再被其他结点指向。

在这里插入图片描述

若链表的指针域指向的是在堆空间申请的内存,则为动态链表,不用考虑链表的长度、是否装满问题。在栈区则需要提前写好且无法更改,顺序结构中的数组也需要提前确定大小,有时要判断是否满载。

静态链表

C语言中可用一维的结构体数组来构造链表,称为静态链表,其中结构体变量包括数据域和游标。其物理形式上是数组,但思想上是链表,此方法可以在没有指针类型的高级语言中使用链表结构。在树的同构判别一节有实例。

数组和链表对比

数组优点:

1、最好情况可以利用偏移地址来访问元素,效率高,为O(1);

2、可以使用折半方法查找元素,效率较高,为O(logN)。

数组缺点:

1、需要连续的空间,存储效率低;

2、插入和删除元素比较麻烦,效率也低。

链表优点:

1、不要求连续空间,空间利用率高。

2、插入和删除元素不需要移动其余元素,效率高,为O(1);

链表缺点:

1、不提高随机访问元素的机制;

2、查找元素和搜索元素的效率低,平均情况为O(N/2),即O(N/)。

数组和链表通常是其他数据结构实现的基础。

因此对于经常插入和删除的操作,数据结构可采用链表或者使用二叉搜索树,反之如果对数据结构有顺序上的要求,通常使用数组更高效,如堆、哈夫曼树;遍历操作如果可以直接通过索引访问,数组的时间复杂度为O(1),此外数组和链表一样都是O(N),但还要考虑空间效率,如果空间也是紧密排列的,比如完全二叉树(还可以索引访问),则选择数组更简单。

线性表(linear list)

必存在首元素和尾元素,且除此之外的其他元素必有唯一的前件和后件。

线性/非线性只考虑逻辑层次,链表依旧是线性表。

实际应用中,常以顺序表、链表、栈、队列、字符串等形式使用。

线性表的顺序存储

线性表的链式存储

栈(stack)

一种线性结构,受操作约束的特殊的线性表,只在栈顶(指针Top)插入、删除,后进先出(LIFO)。

**数据结构中的堆栈指的就是栈(stack),堆(**heap)在下文的树章节。

内存中的是堆区、栈区,是内存模型,应该是一种具体的物理实现吧。

栈的顺序存储

栈的顺序存储结构可由一个一维数组和一个记录栈顶元素位置的变量Top组成。

在这里插入图片描述

栈的链式存储

栈的链式存储结构可由一个单链表实现,叫链栈。由链表的知识可知,栈顶指针Top必须在表头才能同时完成插入和删除操作。先构建一个空的头结点,然后在其后面做插入、删除。

在这里插入图片描述

Top是首结点的地址,即头结点指向的地址,s->next的内容。

栈的应用

表达式求值(中缀表达式转后缀表达式),函数调用及递归实现,深度优先搜索,回溯算法

队列(queue)

一种线性结构,受操作约束的特殊的线性表,一端插入,另一端删除,先进先出(FIFO)。

队列的顺序存储

队列的顺序存储结构通常由一个一维数组、一个记录头元素位置的变量front和一个记录尾元素位置的变量rear组成,通常front指向将要删除的元素的前一位。

在这里插入图片描述

单向队列初始front和rear都为-1,表示空队列,插入元素时rear+1,删除元素时front+1,可以塞满,塞满后即使前面有删除出的空位也无法再插入了,属于一次性使用。

在这里插入图片描述

在这里插入图片描述

而循环队列没有固定位置的头尾,但要至少空一个,只能存储n-1个元素,Front指向第一个元素的前一个,Rear指向最后一个元素。Front和Rear不会为-1,范围应在0-n,两者相等时表示空队列,(Rear+1)%MaxSize等于Front时表示满队列,因为Rear再往后移一位就和Front重合了。

在这里插入图片描述

队列的链式存储

队列的链式存储可用一个单链表实现,由链表的知识可知,用指针front实现删除操作只能在表头,故在表尾用指针rear实现插入操作。

在这里插入图片描述

这张图为非空的头结点,front指向的相当于首结点,即存放第一个元素的位置,或者说front在这里充当了头结点的作用,rear指向尾结点。

例题:多项式链表加法与乘法

链表练习

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<time.h>

int Compare(int a, int b) {
	if (a > b)
		return 1;
	if (a < b)
		return -1;
	return 0;
}

//定义多项式结构体
struct PolyNode {
	int coef;
	int expon;
	struct PolyNode* link;
};
typedef struct PolyNode* Polynomial;

//头插法创建多项式链表
Polynomial createPolynomial(int coef[], int expon[], int size) {
	Polynomial head = NULL;
	for (int i = 0; i < size; i++)
	{
		if (coef[i]!=0)//只存储非零项
		{
			Polynomial temphead = (Polynomial)malloc(sizeof(struct PolyNode));
			if (temphead == NULL) {
				printf("内存分配失败\n");
				exit(0);
			}

			temphead->coef = coef[i];
			temphead->expon = expon[i];
			temphead->link = head;//构造新结点并指向头结点,第一个结点会指向NULL

			head = temphead;//更新头结点
		}
	}
	return head;
}

//从头往后释放链表内存(销毁链表)
void freePolynode(Polynomial head) {
	Polynomial current = NULL;
	while (head)
	{
		current = head->link;
		free(head);
		head=current;
	}
}

//打印多项式链表
void printPolynamial(Polynomial head) {
	//head = head->link;
	while (head)
	{
		printf("%dx^%d ", head->coef, head->expon);
		if (head->link != NULL && head->link->coef > 0)//正系数前打印+号
			printf("+");
		head = head->link;
	}
	printf("\n");
}

//尾插法为结果多项式插入单项式
void attach(int coef,int expon,Polynomial * prear) {
	if (coef!=0) {//系数不为0才插入
		Polynomial temp = (Polynomial)malloc(sizeof(struct PolyNode));
		temp->coef = coef;
		temp->expon = expon;
		temp->link = NULL;
		(*prear)->link = temp;//地址传递保证能改变实参尾结点rear的值
		*prear = temp;
	}
}

//读入多项式,先输入项数,然后分别输入每项系数和阶数
Polynomial ReadPoly() {
	Polynomial P,rear,temp;//多项式的头结点P和尾结点rear,删除头结点临时temp
	int c, e, n;

	scanf("%d", &n);
	P = (Polynomial)malloc(sizeof(struct PolyNode));//链表头空结点
	P->link = NULL;
	rear = P;
	while (n--) {
		scanf("%d %d", &c, &e);
		attach(c, e, &rear);
	}
	temp = P;
	P = P->link;
	free(temp);
	return P;
}
	
//多项式链表加法函数
Polynomial addP(Polynomial P1, Polynomial P2) {
	Polynomial front, rear, temp;//front记录结果多项式头指针,rear记录结果多项式尾结点,temp临时空结点用于释放内存
	int sum;
	front = (Polynomial)malloc(sizeof(struct PolyNode));
	rear = front;
	while(P1&&P2)
		switch (Compare(P1->expon, P2->expon))
		{
		case 1://根据系数数组coef的升降排序不同,case 1,-1也要互换
			attach(P1->coef, P1->expon, &rear);//尾插函数,会改变rear地址,接在原rear后面,第一个也就是front后面
			P1 = P1->link;
			break;
		case -1:
			attach(P2->coef, P2->expon, &rear);
			P2 = P2->link;
			break;
		case 0:
			sum = P1->coef + P2->coef;
			if (sum)attach(sum, P1->expon, &rear);//如果系数之和为0则不插入
			P1 = P1->link;
			P2 = P2->link;
			break;
		}
	for (; P1; P1 = P1->link)attach(P1->coef, P1->expon, &rear);//其中一个多项式没项后,将剩余的直接尾插到结果
	for (; P2; P2 = P2->link)attach(P2->coef, P2->expon, &rear);
	rear->link = NULL;//结果多项式链表最后指向NULL
	temp = front;//attac函数是插入到rear后面,故一开始是一个空结点
	front = front->link;//指向真正的多项式第一项,否则会把空结点的乱码数据也打印出来,当然可以初始化一下
	free(temp);//释放开始的空结点
	return front;
}

//多项式链表乘法函数1(每轮加法,每项计算完一轮加到结果多项式)
Polynomial multP1(Polynomial P1, Polynomial P2) {
	Polynomial front,rear,t1,t2,result;//front单轮乘法结果多项式头结点,rear尾结点,t1,t2接收2个多项式,result结果多项式
	result = (Polynomial)malloc(sizeof(struct PolyNode));
	result->coef = 0;
	result->expon = 0;
	result->link = NULL;
	t1 = P1;
	while (t1)//取t1每一项做乘法,对t2从头开始做一轮乘法
	{
		t2 = P2;//t2指向P2头结点
		front = (Polynomial)malloc(sizeof(struct PolyNode));//创建单轮乘法结果多项式
		front->link = NULL;
		rear = front;
		while (t2)
		{
			attach(t1->coef * t2->coef, t1->expon + t2->expon, &rear);
			t2 = t2->link;
		}
		t2 = front;//借用t2释放单轮乘法结果多项式头结点
		front = front->link;
		free(t2);
		//printPolynamial(front);
		result = addP(result, front);//将单轮乘法结果多项式加到结果多项式
		free(front);//乘完一轮把front释放掉
		//printPolynamial(result);
		t1 = t1->link;//取t1下一项继续循环
	}
	//t1 = result;//从结果看result没有空头结点
	//result = result->link;
	//free(t1);
	return result;
}

//多项式链表乘法函数2(逐项插入,每计算一小项就插入到结果链表)
Polynomial multP2(Polynomial P1, Polynomial P2) {
	if (!(P1 && P2))return NULL;//当P1或P2中有空指针,等价于!P1||!P2,因为乘法不能有空值,加法可以
	
	Polynomial front, rear, t1, t2, temp;//front结果多项式头结点,rear尾结点,t1,t2接收2个多项式,temp插入临时结点
	int c , e ;
	t1 = P1;
	t2 = P2;
	front = (Polynomial)malloc(sizeof(struct PolyNode));//创建空结点,后接单项乘法结果多项式
	front->link = NULL;
	rear = front;
		while (t2)//先构建一个初始多项式结果,t1的第一项乘以t2
	{
		attach(t1->coef * t2->coef, t1->expon + t2->expon, &rear);
		t2 = t2->link;
	}
	t1 = t1->link;
	while (t1) {
		t2 = P2;
		rear = front;
		while (t2)
		{
			c = t1->coef * t2->coef;
			e = t1->expon + t2->expon;
			while(rear->link && rear->link->expon > e)rear = rear->link;//因为结果降幂排序,遍历结果多项式跳过所有指数比e大的
			if (rear->link && rear->link->expon == e)//如果当前rear的下一个指数正好等于e,则数据相加,否则插入一个新的结点
			{
				if (rear->link->coef + c)
				{
					rear->link->coef = rear->link->coef + c;
				}
				else//如果和下个结点相加后系数为0则删除结点
				{
					temp = rear->link;
					rear->link = temp->link;
					free(temp);
				}
			}
			else {
				temp = (Polynomial)malloc(sizeof(struct PolyNode));
				temp->coef = c;
				temp->expon = e;
				temp->link = rear->link;
				rear->link = temp;
				rear = rear->link;
			}
			t2 = t2->link;
		}
		t1 = t1->link;
	}
	t2 = front;//借用t2把开始的空结点删除
	front = front->link;
	free(t2);
	return front;
}

int main()
{
	//int coef1[] = { -1, 2, 0, -1, 4, 3 };
	//int coef2[] = { 0, 1, -7, 1, 2, 0 };
	//int expon[] = { 0, 1, 2, 3, 4, 5 };

	//Polynomial P1 = createPolynomial(coef1, expon, 6);
	//Polynomial P2 = createPolynomial(coef2, expon, 6);

	Polynomial P1 = ReadPoly();
	printPolynamial(P1);
	Polynomial P2 = ReadPoly();
	printPolynamial(P2);

	Polynomial PS = addP(P1, P2);
	printPolynamial(PS);
	Polynomial PM1 = multP1(P1, P2);
	printPolynamial(PM1);
	Polynomial PM2 = multP2(P1, P2);
	printPolynamial(PM2);

	freePolynode(P1);
	freePolynode(P2);
	freePolynode(PS);
	freePolynode(PM1);
	freePolynode(PM2);

	return EXIT_SUCCESS;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值