数据结构笔记


一、数组

int a[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
1、稀疏矩阵的存储方法是三元组或十字链表。三元组可以大大节省空间,但是做矩阵运算时需要大量移动元素,十字链表可以避免大量移动元素。对于三元组来说是将非零元素所在的行、列以及它的值构成一个三元组(i,j,v),如果一个稀疏矩阵是以三元组的形式存储的,那么要对这个矩阵转置时,需要将三元组的行列互换,然后按行排列即可。
2、在C++中,定义数组,必须有行。
3、数组和链表的区别
  数组是将元素在内存中连续存放,由于每个元素占用的内存相同,可以通过下标迅速访问数组中的任何元素。数组的插入数据和删除数据效率低。但是数组随机读取效率很高,数组需要预留空间,在使用前要先申请占内存的大小,可能会浪费内存空间,数组不利于扩展。
  链表中的元素在内存中不是顺序存储的,如果要访问链表中的一个元素比较低效,删除和插入比较高效。链表不指定大小,扩展方便,数据随意增删,内存利用率比较高。

二、字符串

string num=‘hello’
num.substr(0, i + 1)为’hell‘

三、链表

1、带头结点单向链表的判空条件是head.next= =null; 带头结点的单向循环链表的判空条件是head.next==head;
2、(例题)设单循环链表中节点的结构为(data,next),且rear是指向非空的带头节点的单循环链表的尾节点的指针。若想删除链表第一个数据元素(首元节点),则应执行下列哪一个操作?
在这里插入图片描述
注意第一个rear的位置,指的是那部分。
3、什么是单向链表,如何判断两个单向链表是否相交
  单向链表是链表的一种,其特点是链表的连接方向是单向的,对链表的访问要通过顺序读取从头部开始;链表是使用指针进行构造的列表,链表是有一个个节点组装起来的;其中每个节点都有指针成员变量指向列表中的下一个节点。
在这里插入图片描述  判断两个链表相交可以使用两个指针分别从两个链表的头部走向尾部,最后判断尾部指针的地址信息是否一样来判断链表是否相交;还可以把一个链表的地址信息存储到哈希表中,然后把遍历另一个链表的每一个节点地址信息,若位于哈希表中,则跳出循环,表明链表相交。

#include <bits/stdc++.h>

using namespace std;

//定义一个结点模板
template<typename T>
struct Node {
	T data;
	Node *next;
	Node() : next(nullptr) {}
	Node(const T &d) : data(d), next(nullptr) {}
};

//删除 p 结点后面的元素
template<typename T>
void Remove(Node<T> *p) {
	if (p == nullptr || p->next == nullptr) {
		return;
	}
	auto tmp = p->next->next;
	delete p->next;
	p->next = tmp;
}

//在 p 结点后面插入元素
template<typename T>
void Insert(Node<T> *p, const T &data) {
	auto tmp = new Node<T>(data);
	tmp->next = p->next;
	p->next = tmp;
}

//遍历链表
template<typename T, typename V>
void Walk(Node<T> *p, const V &vistor) {
	while(p != nullptr) {
		vistor(p);
		p = p->next;
	}
}

int main() {
	auto p = new Node<int>(1);
	Insert(p, 2);
	int sum = 0;
	Walk(p, [&sum](const Node<int> *p) -> void { sum += p->data; });
	cout << sum << endl;
	Remove(p);
	sum = 0;
	Walk(p, [&sum](const Node<int> *p) -> void { sum += p->data; });
	cout << sum << endl;
	return 0;
}

四、栈

1、在栈中、栈顶的动态变化决定栈中元素的个数;栈具有记忆功能。
2、stack overflow,举个简单例子导致栈溢出
  栈一处指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致栈中与其相邻的变量的值被改变。栈溢出的原因:

  • 递归的调用层次太多,递归函数在运行时会执行压栈操作,当压栈次数太多时,会导致溢出;
  • 指针或者数组越界,如进行字符串拷贝时strcpy
  • 局部数组过大,当函数内部的数组过大时,很有可能导致堆栈溢出,因为局部变量是存储在栈中的。可以通过增大栈空间或者使用动态分配来解决。
    3、堆和栈的区别
    堆是由低地址向高地址扩展;栈是由高地址向低地址扩展
    堆中的内存是需要手动申请和手动释放;栈中的内存是由操作系统自动申请和释放,存放着参数、局部变量等内存。
    堆中频繁调用malloc和free,会产生内存碎片,降低程序效率;而栈由于先入后出的特性,不会产生内存碎片。
    栈是由操作系统提供的数据结构,计算机底层有分配专门寄存器来存储栈的地址,压栈和入栈有专门的指令进行,而堆是由C++函数库提供的,机制相对复杂,需要一些分配内存、释放内存的算法,因此堆的分配效率较低,栈的分配效率较高。

五、队列

六、树

  树是有一个集合以及在该集合上定义的一种关系构成的。节点的度意为一个节点所含有的子树的个数。
二叉树:二叉树的每个节点至多只有两颗子树,有左右之分。、
在这里插入图片描述
满二叉树:
在这里插入图片描述
完全二叉树:
  对一颗具有N个节点的二叉树按层次从左到右编序,二叉树某个节点的编序与同样位置的满二叉树节点的编序一一对应时,这个二叉树成为完全二叉树。满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。

二叉搜索树:
  如果他的左子树不为空,则左子树上所有节点的值都小于根节点的值;如果他的右子树不为空,那么右子树上所有节点的值都大于根节点的值,他的左右子树都是二叉搜索树。这样的特性便于进行查找删除插入等一系列操作,时间复杂度为O(log(n))。二叉搜索树的中序遍历就是顺序遍历

//recursive
class Solution1 {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ret;
        if(root==NULL)return ret;
        inorderHelper(ret,root);
        return ret;
    }
private:
    void inorderHelper(vector<int>& ret,TreeNode* root)
    {
        if(root==NULL)return;
        inorderHelper(ret,root->left);
        ret.push_back(root->val);
        inorderHelper(ret,root->right);
    }
};

平衡二叉树<AVL树>:
  树中所有节点为根的树的左右子树高度之差不超过1。将二叉树上节点的左子树高度减去右子树高度的值称为该节点的平衡因子。
  AVL树的自平衡操作–旋转
  平衡二叉树最大的作用就是查找,平衡二叉树的查找、插入和删除时间复杂度都是O(log(n))。如果平衡二叉树插入或删除节点,使节点的高度之差大于1,那么AVL树的平衡就会被破坏,为了要让他重新维持在一个平衡状态,要进行旋转处理,因此创建一个平衡二叉树的成本不小,出现了红黑树。

红黑树:
  红黑树是一颗二叉搜索树,它在每个节点增加了一个存储位记录节点的颜色,可以是红可以是黑,红黑树保证最长路径不超过最短路径的二倍。
具体性质:根节点是黑色的;没有连续的红节点;对于每个节点,从该节点到其后代叶节点的简单路径上,均包含相同数目的黑节点。
在这里插入图片描述
哈夫曼编码:
  哈夫曼编码是哈曼夫树的一种应用,广泛用于数据文件压缩。哈夫曼编码算法用字符在文件中出现的频率来建立使用0,1表示每个字符的最优表示方式,具体算法如下:

  • 哈夫曼算法以自低向上的方式构造表示最优前缀码的二叉树T。
  • 算法以|C|个叶节点开始,执行|C|-1次合并运算之后产生最终要求的树T
  • 假设编码字符集的每一字符c的频率是f©,以f为键值的优先队列Q用贪心选择有效的确定算法当前要合并的2棵具有最小频率的树,一旦最小频率的树合并以后,产生一颗新的树,其频率为合并的2棵树之和。经过n-1次合并后,生成所要求的树。

七、图

八、哈希

  map的底层是用红黑树实现的,查找的时间复杂度为log(n);hash_map的底层是hash表存储的,查询时间复杂度为O(1),hash表把数据查找所消耗的时间大大降低,而代价是消耗更多的内存,它的基本原理是:使用一个下标范围比较大的数组来存储元素,可以设计一个函数(哈希函数)使得每个元素的关键字都与一个函数值相对应,于是用这个数组单元来存储这个元素,但是不能保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了冲突,换句话就是把不同的元素分在了相同的类之中,一般来说,直接定址和解决冲突是哈希表的两大特点。在选择上,hash_map的查找速度比map快,属于常数级别,但是还有hash函数的耗时,因此如果在数据量比较大,需要考虑效率时,可以使用hash_map,但如果对内存要求严格,则可以选择内存消耗较小的map。
哈希冲突的解决方法:

  • 开放地址法:所有输入的元素全部存放在哈希表里,位桶的实现不需要任何的链表来实现的,也就是这个哈希表的装载因子不会超过1.他的实现是,在插入一个元素的时候,先通过哈希函数进行判断,若是发生哈希冲突,就以当前地址为基准,根据再寻址的方法去寻找下一个地址,知道找到一个空的地址为止。
  • 链地址:每个位桶实现的时候,采用链表或者树的数据结构来去存储发生哈希冲突的输入域的关键字,也就是被哈希函数映射到同一个位桶上的关键字。
  • 公共溢出区:建立一个公共溢出区域,把hash冲突的元素都放在该溢出区里。查找时,如果发现hsah表中对应的桶里存在其他元素,还需要在公共溢出区里进行再次查找。

九、堆

1、堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:堆中某个结点的值总是不大于或不小于其父结点的值;堆总是一棵完全二叉树。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值