【从零开始数据结构】线性表

197 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

数据结构

第一章 绪论

什么是数据结构?

  • 解决问题方法的效率,跟数据的组织方式有关
  • 解决问题方法的效率,跟空间的利用效率有关
  • 解决问题方法的效率,跟算法的巧妙程度有关

什么是算法?

  • 一个有限指令集
  • 接受一些输入(有的情况不需要输入)
  • 产生输出
  • 一定在有限步骤后终止
  • 每一条指令必须:
    • 有充分明确的目标,不可以有歧义
    • 计算机能处理的范围之内
    • 描述应不依赖于任何一种计算机语言以及具体的实现手段

什么是好的算法?

空间复杂度S(n)——根据算法写成的程序在执行时占用存储单元的长度。

  • 这个长度往往与输入数据的规模有关。
  • 空间复杂度过高的算法可能导致使用的内存超限,造成程序非正常中断。

时间复杂度T(n)——根据算法写成的程序在执行时耗费的时间的长度。

  • 这个长度往往也与输入数据的规模有关。
  • 时间复杂度过高的低效算法可能导致我们在有生之年都等不到运行结果

在分析一般算法的效率时,我们经常关注下面两种复杂度:

  • 最坏情况复杂度Tworst(n)
  • 平均复杂度Tavg(n)
  • Tavg(n) <= Tworst(n)

复杂度分析小窍门

  • 若两段算法分别有复杂度T1(n) = O(f1(n)) 和 T2(n) = O(f2(n)),则

    • T1(n) + T2(n) = max( O(f1(n)), O(f2(n)) )
    • T1(n) * T2(n) = O( f1(n) * f2(n) )
  • 若T(n)是关于n的k阶多项式,那么T(n) = O(n^k) // 这里的O中间有一圆点

  • 一个for循环的时间复杂度等于循环次数乘以循环体代码的复杂度

  • if-else结构的复杂度取决于if的条件判断复杂度和两个分支部分的复杂度,总体复杂度取三者中最大

第二章 线性表

引:多项式表示——链表结构存储非零项

链表中每个结点存储多项式中的一个非零项,包括系数和指数两个数据域以及一个指针域

typedef struct PolyNode *Polynomial;
struct PloyNode {
    int coef;
    int expon;
    Polynomial link;
}

什么是线性表?

线性表(Linear List):由同类型数据元素构成有序序列的线性结构

  • 表中元素个数称为线性表的 长度
  • 线性表没有元素时,称为 空表
  • 表起始位置称为 表头,表结束位置称为 表尾

线性表的顺序存储实现

利用数组的 连续存储空间顺序存放 线性表的各元素

主要操作的实现:

#include <iostream>

using namespace std;

// 扩充容量的步伐
#define sizestep 10
// 开始的容量大小
#define startsize 100

typedef int ElemType;
struct List {
	ElemType* data;	// 数据
	int length;		// 长度
	int size;		// 初始容量
};

// 创建一个空的线性表
void InitList(List& newList)
{
	newList.size = startsize;	// 初始容量为startsize;
	newList.data = new ElemType[newList.size];	// 开辟空间
	newList.length = 0;			// 空表长度为0
}

// 销毁线性表
void DestroyList(List& newList)
{
	newList.length = 0;
	// 一定要先释放堆内存
	delete[] newList.data;
	// 释放堆内存后,并将对应的指针赋予nullptr是个良好的习惯
	newList.data = nullptr;
}

// 清空线性表
void ClearList(List& newList)
{
	newList.length = 0;
	delete[] newList.data;
	newList.data = nullptr;
	// 重新为存放元素的变量开辟一个新的堆内存
	newList.data = new ElemType[newList.size];
}

// 判断线性表是否为空
bool ListEmpty(List newList)
{
	return newList.length;
}

// 返回线性表的长度
int ListLength(List newList)
{
	return newList.length;
}

// 获取线性表上某个位置上的元素的值(位置从1开始计算)
void GetElem(List newList, int i, ElemType& e)
{
	if (ListEmpty(newList)) {
		cout << "线性表为空" << endl;
		return;
	}
	if (i < 1 || i > newList.length) {
		cout << "当前位置超出线性表范围" << endl;
		return;
	}
	e = newList.data[i - 1];
}

// 获取元素的位置(这里直接返回该元素下标,而不是从1开始)
int LocationElem(List newList, ElemType e)
{
	for (int i = 0; i < newList.length; ++i) {
		if (newList.data[i] == e)
			return i;
	}
	return -1;
}

// 获取前驱元素
void PriorElem(List newList, ElemType cur_e, ElemType& pre_e)
{
	int location = 0;
	location = LocationElem(newList, cur_e);
	// 如果location为 -1,说明cur_e不在线性表中
	if (location == -1) {
		cout << cur_e << "不在线性表中" << endl;
		return;
	}
	// 如果location为 0,说明cur_e在线性表的第一个位置,没有前一个元素
	if (location == 0) {
		cout << cur_e << "是线性表的第一个元素,没有前驱" << endl;
		return;
	}
	pre_e = newList.data[location - 1];
}

// 获取后驱元素
void NextElem(List newList, ElemType cur_e, ElemType& next_e)
{
	int location = 0;
	location = LocationElem(newList, cur_e);
	// 如果location为 -1,说明cur_e不在线性表中
	if (location == -1) {
		cout << cur_e << "不在线性表中" << endl;
		return;
	}
	// 如果location为 newList.length - 1,说明cur_e在线性表的最后一个位置,没有后一个元素
	if (location == newList.length - 1) {
		cout << cur_e << "是线性表的最后一个元素,没有后驱" << endl;
		return;
	}
	next_e = newList.data[location + 1];
}

// 向线性表中插入一个元素,需要判断插入位置的合法性(从1开始)
void InsertList(List& newList, int i, ElemType e)
{
	// 插入位置不合法
	if (i < 1 || i > newList.length + 1) {
		cout << "请检查插入位置是否正确" << endl;
		return;
	}
	int j = 0;
	// 如果达到了线性表的最大容量,需要重新为线性表分配新的内存
	if (newList.length == newList.size) {
		// 先保存之前的内容
		ElemType* p = new ElemType[newList.length];
		for (j = 0; j < newList.length; ++j) {
			p[j] = newList.data[j];
		}
		// 扩大容量
		newList.size += sizestep;
		delete[] newList.data;
		// 重新分配内存
		newList.data = new ElemType[newList.size];
		// 恢复之前内容
		for (j = 0; j < newList.length; ++j) {
			newList.data[j] = p[j];
		}
	}

	// 插入内容
	for (int k = newList.length; k > i - 1; --k) {
		newList.data[k] = newList.data[k - 1];
	}
	newList.data[i - 1] = e;
	++newList.length;
}

// 删除一个元素(从1开始算起)
void DeleteList(List& newList, int i)
{
	// 删除位置不合法
	if (i < 1 || i > newList.length) {
		cout << "请检查删除位置是否合法" << endl;
		return;
	}
	for (int j = i - 1; j < newList.length; ++j) {
		newList.data[j] = newList.data[j + 1];
	}
	--newList.length;
}

// 按照元素的值,删除对应元素的内容
// 通过传入参数,来决定删除第一个还是所有
// 0——删除第一个,1——删除所有
void Delete_dataList(List& newList, ElemType e, int order)
{
	int flag = 0;
	for (int i = 0; i < newList.length; ++i) {
		if (newList.data[i] == e) {
			flag = 1;
			// 删除对应位置上的元素,而且i也要减少一个
			DeleteList(newList, i + 1);
			--i;
			if (order == 0)
				return;
		}
	}
	if (flag == 1)
		return;
	cout << e << "不在线性表中" << endl;
}

// 链接两个线性表(当我们进行链接的时候,最好是希望两个链表有序)
void Connece_two_List(List a, List b, List& c)
{
	// 对c进行一些数据初始化
	c.length = c.size = a.length + b.length;
	c.data = new ElemType[c.size];

	// 采用指针的方式进行数据的移动,先把a和b数据第一个和最后一个元素的位置找出来
	int i = 0;
	int j = 0;
	int k = 0;

	while (i <= a.length - 1 && j <= b.length - 1) {
		if (a.data[i] < b.data[j]) {
			c.data[k++] = a.data[i++];
		}
		else if (a.data[i] > b.data[j])
			c.data[k++] = b.data[j++];
		else {
			c.data[k++] = b.data[j++];
			--c.length;
		}
	}

	// 处理剩余一方的元素
	while (i <= a.length - 1) {
		c.data[k++] = a.data[i++];
	}
	while (j <= b.length - 1) {
		c.data[k++] = b.data[j++];
	}
}

// Print输入
void print(List& L)
{
	for (int i = 0; i < L.length; ++i) {
		cout << L.data[i] << " ";
	}
	cout << endl;
}

线性表的链式存储实现

不要求逻辑上相邻的两个元素物理上也相邻;通过“链”建立起数据元素之间的逻辑关系。

  • 插入、删除不需要移动数据元素,只需要修改“链”。

主要操作的实现:

#include <iostream>

using namespace std;

typedef int ElemType;
// 保存长度的头结点,加入这个结点可以方便一些基本的操作
struct Node {
	ElemType value;
	Node* next;		// 下一结点地址
};

// 创建一个空链表
void InitList(Node*& head)
{
	head = new Node();
	head->value = 0;
	head->next = nullptr;
}

// 销毁单链表
void DestroyList(Node*& head)
{
	Node* p;
	while (head) {
		p = head;
		head = head->next;
		delete p;
	}
}

// 清空单链表
void ClearList(Node*& head)
{
	Node* p;
	while (head->next) {
		p = head;
		head = head->next;
		delete p;
	}
	head->value = 0;
}

// 判断链表是否为空,为空返回true
bool ListEmpty(Node* head)
{
	return head->value == 0;
}

// 返回链表的长度
bool ListLength(Node* head)
{
	return head->value;
}

// 得到某个位置上的值
bool GetElem(Node* head, int i, ElemType& value)
{
	// 想要得到的那个位置是否合法
	if (i < 1 || i > head->value)
		return false;

	int j = 0;
	Node* newhead = head;
	while (j < i - 1) {
		newhead = newhead->next;
		++j;
	}
	value = newhead->next->value;
	return true;
}

// 根据值得到该结点的地址以及序号
bool LocateElem(Node* head, ElemType value, Node*& address, int& order)
{
	order = 1;
	Node* temp = head->next;
	while (temp && temp->value != value) {
		temp = temp->next;
		++order;
	}
	if (temp) {
		address = temp;
		return true;
	}
	else {
		address = nullptr;
		order = -1;
		return false;
	}
}

// 得到某个元素前面的那个元素的值
bool PriorElem(Node* newhead, ElemType cur_e, ElemType& pre_e)
{
	while (newhead->next) {
		if (newhead->next->value == cur_e) {
			pre_e = newhead->value;
			return true;
		}
		newhead = newhead->next;
	}
	return false;
}

// 得到某个元素的下一个元素
bool NextElem(Node* newhead, ElemType cur_e, ElemType& next_e)
{
	newhead = newhead->next;	// 从首元结点开始
	while (newhead->next) {
		if (newhead->value == cur_e) {
			next_e = newhead->next->value;
			return true;
		}
		newhead = newhead->next;
	}
	return false;
}

// 插入一个元素
bool InsertList(Node*& head, int i, ElemType value)
{
	// 如果插入位置不合法,返回错误提示
	if (i < 1 || i > head->value + 1)
		return false;

	// 得到插入位置的前一个结点
	int j = 0;
	Node* temp = head;
	while (j < i - 1) {
		temp = temp->next;
		++j;
	}

	// s是一个临时结点
	Node* s = new Node();
	s->value = value;
	s->next = temp->next;
	temp->next = s;
	
	++head->value;
	return true;
}

// 根据下标的值进行删除,并返回删除信息
bool DeleteList(Node*& head, int i, ElemType& value)
{	
	// 如果删除位置不合法,返回错误提示
	if (i < 1 || i > head->value)
		return false;

	// 先找到需要删除结点的前一个结点
	int j = 0;
	Node* temp = head;
	while (j < i - 1) {
		temp = temp->next;
		++j;
	}

	//删除结点
	Node* del = temp->next;
	temp->next = del->next;
	value = del->value;
	delete del;
	return true;
}

// 根据值进行删除,根据标志位删除 第一次出现/所有的该值
// flag = 0 删除第一次    flag = 1 删除所有的该值
bool Delete_data_List(Node*& head, ElemType value, int flag)
{
	ElemType save = -1;	// 临时保存要删除的数据

	// 遍历整个线性表删除结点
	int sign = 0;	// 删除成功or失败
	int j = 0;
	Node* temp = head;
	while (temp->next) {
		temp = temp->next;
		++j;

		if (temp->value == value) {
			DeleteList(head, j, save);
			// 删除了一个,当前位置也会往前推一个
			--j;
			sign = 1;	// 删除成功
			if (flag == 0)
				break;
		}
	}
	if (sign == 0)
		return false;
	return true;
}

// 连接两个链表,默认已经排好序
void Connect_two_List(Node* a, Node* b, Node* c)
{
	c->value = a->value + b->value;
	int a_value = a->value;
	int b_value = b->value;

	int i = 1;
	int j = 1;
	int k = 1;
	Node* a_temp = a->next;
	Node* b_temp = b->next;

	while (i <= a_value && j <= b_value) {
		if (a_temp->value < b_temp->value) {
			++i;
			InsertList(c, k, a_temp->value);

			++k;
			a_temp = a_temp->next;
		}
		else if (a_temp->value > b_temp->value) {
			++j;
			InsertList(c, k, b_temp->value);

			++k;
			b_temp = b_temp->next;
		}
		else {
			++j;
			++i;
			InsertList(c, k, a_temp->value);

			--c->value;
			++k;
			a_temp = a_temp->next;
			b_temp = b_temp->next;
		}
	}

	while (i <= a_value) {
		++i;
		InsertList(c, k, a_temp->value);
		++k;
		a_temp = a_temp->next;
	}
	while (j <= b_value) {
		++i;
		InsertList(c, k, b_temp->value);
		++k;
		b_temp = b_temp->next;
	}
}

广义表与多重链表

什么是广义表?

  • 广义表是线性表的推广
  • 对于线性表而言,n个元素都是基本的 单元素
  • 广义表中,这些元素不仅可以是单元素也可以是 另一个广义表
typedef struct GNode* GList;
struct GNode {
    int Tag;	// 标志域:0表示结点是单元素,1表示结点是广义表
    union {		// 子表指针域SubList与单元素数据域Data复用,即共用存储空间
        ElementType Data;
        GList SubList;
    }URegion;
    GList Next;		// 指向后继结点
}

什么是多重链表?

链表中的节点可能同时隶属于多个链

  • 多重链表中结点的 指针域会有多个,如前面的Next和SubList两个指针域
  • 但包含两个指针域的链表并不一定是多重链表,比如 双向链表不是多重链表

多重链表有广泛的用途:如树、图这样相对复杂的数据结构都可以采用多重链表方式实现存储。