1.队列
1.1 使用数组实现队列
原理待补充...
#include <iostream>
//使用数组来实现队列
template <typename T,int capacity>
class Queue {
private:
T* arr;//储存队列的数组指针
int count;//储存队列中元素个数
int rear;//队尾
int front;//队首
public:
Queue() :rear(0), front(0), count(0)
{
arr = new T[capacity];
};
~Queue()
{
delete[] arr;
};
int getFront()
{
return front;
}
int getRear()
{
return rear;
}
bool isEmpty()
{
return count == 0;
};
//入队 从队尾入
bool enqueue(T elem)
{
//若队列满了则直接返回
if (count >= capacity)
return false;
if (isEmpty())
{
arr[rear] = elem;
count++;
}
else
{
rear = (rear + 1) % capacity;//循环数组
arr[rear] = elem;
count++;
}
std::cout << "enqueue: " << elem<<std::endl;
return true;
};
//出队 从队头出
bool dequeue()
{
if (isEmpty())
return false;
count--;
std::cout << "dequeue: " << arr[front] << std::endl;
front = (front + 1) % capacity;
return true;
}
//打印整个队列
void printQueue()
{
if (isEmpty())
{
std::cout << "empty queue\n";
return;
}
std::cout << "queue:\n";
if (front <= rear)
{
for (int i = front; i <= rear; i++)
std::cout << arr[i] << " ";
}
else
{
for (int i = front; i <capacity; i++)
{
std::cout << arr[i] << " ";
}
for (int i = 0; i <= rear; i++)
{
std::cout << arr[i] << " ";
}
}
std::cout << "\n";
}
};
//测试程序
int main()
{
Queue<int, 6> myqueue;
myqueue.enqueue(2);
myqueue.enqueue(2);
myqueue.enqueue(2);
myqueue.enqueue(2);
myqueue.enqueue(3);
myqueue.enqueue(4);
myqueue.printQueue();
myqueue.dequeue();
myqueue.dequeue();
myqueue.dequeue();
myqueue.dequeue();
myqueue.printQueue();
myqueue.enqueue(5);
myqueue.enqueue(6);
myqueue.printQueue();
}
输出:
2 二叉搜索树
#include <iostream>
template <typename T>
struct BSTNode
{
BSTNode<T> *left;
BSTNode<T> *right;
T data;
BSTNode(T DATA) : left(nullptr), right(nullptr), data(DATA){};
};
template <typename T>
class BST
{
private:
BSTNode<T> *root;
int count;
BSTNode<T> *insertBSTNode(T data, BSTNode<T> *ptr)
{
if (ptr == nullptr)
{
count++;
return new BSTNode<T>(data);
}
else
{
if (data > ptr->data)
{
ptr->right = insertBSTNode(data, ptr->right);
}
if (data < ptr->data)
{
ptr->left = insertBSTNode(data, ptr->left);
}
return ptr;
}
}
//中序遍历
void inOrderTraversal(BSTNode<T> *ptr)
{
if (ptr == nullptr)
return;
inOrderTraversal(ptr->left);
std::cout << "\t" << ptr->data;
inOrderTraversal(ptr->right);
}
//判断是否是叶子节点
bool isLeaf(BSTNode<T> *node)
{
return node->left == nullptr && node->right == nullptr;
}
public:
BST(T data) : root(nullptr), count(0) {}
~BST()
{
deleteAllNodes(root);
}
int getCount()
{
return count;
}
void deleteAllNodes(BSTNode<T> *root)
{
if (root == nullptr)
{
return;
}
deleteAllNodes(root->left);
deleteAllNodes(root->right);
delete root;
}
void insert(T data)
{
//空树的情况
if (root == nullptr)
{
root = new BSTNode<T>(data);
count++;
}
else
insertBSTNode(data, root);
}
void remove(T data)
{
//找到指定的节点以及其父节点
BSTNode<T> *ptr = root;
BSTNode<T> *parent = nullptr;
while (ptr != nullptr && data != ptr->data)
{
if (data > ptr->data)
{
parent = ptr;
ptr = ptr->right;
}
else if (data < ptr->data)
{
parent = ptr;
ptr = ptr->left;
}
}
//若无法找到想要的节点,直接返回
if (ptr == nullptr)
{
std::cout << "cant find the node to delete\n";
return;
}
//删除的节点是根节点且根节点的的子节点个数小于等于1个
if (ptr == root && (ptr->right == nullptr || ptr->left == nullptr))
{
if (ptr->left == nullptr)
{
BSTNode<T> *temp = root;
root = root->right;
count--;
delete temp;
printTreeInOder();
return;
}
else
{
BSTNode<T> *temp = root;
root = root->left;
count--;
delete temp;
printTreeInOder();
return;
}
}
//若要删除的节点是叶子节点 直接删除
if (isLeaf(ptr))
{ //若直接删除ptr,置局部指针ptr=nullptr,对原删除的节点是无效的!!!!
//因为只是释放了内存,原删除的节点的父节点的子节点指针依旧存在,且并不是nullptr
//ptr在parent左边
if (parent->data > ptr->data)
{
delete ptr;
parent->left = nullptr;
}
else
{
delete ptr;
parent->right = nullptr;
}
//ptr->data
count--;
printTreeInOder();
return;
}
//若要删除的节点只有一个子节点,且不是根节点
if ((ptr->left == nullptr && ptr->right != nullptr) || (ptr->right == nullptr && ptr->left != nullptr))
{
BSTNode<T> *grandpa = parent;
BSTNode<T> *grandson = ptr->left == nullptr ? ptr->right : ptr->left;
if (grandson->data > grandpa->data)
{
grandpa->right = grandson;
delete ptr;
count--;
printTreeInOder();
return;
}
if (grandson->data < grandpa->data)
{
grandpa->left = grandson;
delete ptr;
count--;
printTreeInOder();
return;
}
}
//若要删除的节点有两个子节点,先访问左节点,然后不断访问右子节点到节点没有右子节点为止
//这个节点就是所有小于要删除节点的节点中最接近删除节点的值
//交换这两个节点的值,然后删除没有右节点的节点
if (ptr->left != nullptr && ptr->right != nullptr)
{
BSTNode<T> *leftMax = ptr->left;
BSTNode<T> *parent = ptr; //储存父节点
//找到左子树的最大节点
//若leftMax已经是叶子
if (isLeaf(leftMax))
{ //交换data
T temp = leftMax->data;
leftMax->data = ptr->data;
ptr->data = temp;
delete leftMax;
parent->left = nullptr;
count--;
return;
}
while (leftMax->right != nullptr)
{
parent = leftMax;
leftMax = leftMax->right;
}
//交换data
T temp = leftMax->data;
leftMax->data = ptr->data;
ptr->data = temp;
//删除leftMax
if (isLeaf(leftMax))
{
delete leftMax;
parent->right = nullptr;
count--;
return;
}
//当leftMax有一个左节点时
else
{
BSTNode<T> *temp = leftMax->left;
if (temp->data > parent->data)
{
parent->right = temp;
delete leftMax;
count--;
return;
}
else
{
parent->left = temp;
delete leftMax;
count--;
return;
}
}
}
}
void printTreeInOder()
{
if (root == nullptr)
{
std::cout << "\nEmpty Tree!";
return;
}
std::cout << "\nIn order:";
inOrderTraversal(root);
std::cout << "\n";
}
};
int main()
{
//BSTNode<int> root(10);
BST<int> myBST(10);
myBST.insert(10);
myBST.insert(19);
//myBST.remove(10); //测试移除单子根节点
myBST.insert(9);
myBST.insert(91);
myBST.insert(13);
myBST.insert(4);
myBST.insert(7);
myBST.insert(70);
myBST.remove(19);
myBST.printTreeInOder();
system("PAUSE");
}
3.堆
3.1 最小堆
#include <iostream>
//定义抽象接口优先队列
template <typename T>
class PriorityQ
{
public:
virtual void insertItem(T item) = 0;
virtual T removeMin() = 0;
virtual bool isEmpty() const = 0;//函数名后的const 代表该函数只能读取对象成员变量,不可修改
virtual int getCount()const = 0;
};
//minheap implemented by array
template <typename T>
class MinHeap:PriorityQ<T>
{
public:
MinHeap();
~MinHeap();
void insertItem(T item);//插入一个item
T removeMin();//最小的item出队列
bool isEmpty() const;//判断队列是否为空
int getCount()const;//获取队列中的元素个数
private:
T heap[100] = {0};//储存堆的数组
int count;
void swap(int bubb, int pare);//输入两个下标,交换两个下标的数组元素
};
template <typename T>
MinHeap<T>::MinHeap() :count(0)
{};
template <typename T>
MinHeap<T>::~MinHeap()
{};
template <typename T>
int MinHeap<T>::getCount()const
{
return count;
}
template <typename T>
void MinHeap<T>::swap(int bubb, int pare)
{
T temp = heap[bubb];
heap[bubb] = heap[pare];
heap[pare] = temp;
}
template <typename T>
bool MinHeap<T>::isEmpty()const
{
return count==0;
}
template <typename T>
void MinHeap<T>::insertItem(T item)
{
if (count == 0)
{
heap[0] = item;
count++;
return;
}
heap[count]=item;//插入一个值到数组末尾
//先在数组最后插入一个叶子,若叶子小于父节点,与父结点交换,不断重复,直到叶子大于父节点为止
int bubble ;
//若一个节点下标为x,则其左节点下标为2x+1,右节点为2x+2
int parent;
bubble = count;
parent= (bubble - 1) / 2;
while (heap[bubble] < heap[parent])
{
swap(bubble,parent);
parent = (bubble - 1) / 2;
}
count++;
return;
}
//移出最小值 伪代码如下
//temp = value of root
//swap root value with last leaf
//delete last leaf
//v = root
//while v > any child(v) {
// swapElements(v, smaller child(v))
// v = smaller child(v)
//}
//return temp
//第三步在对数组排序时可以省略
template <typename T>
T MinHeap<T>::removeMin()
{
if (isEmpty())
return NULL;
T temp = heap[0];
swap(0, count-1);//交换最后一个节点与根节点
int bubble = 0;
int left = 1;
int right = 2;
count--;
while ((heap[bubble] > heap[left] || heap[bubble] > heap[right])&&right<=(count-1)&&left<=(count-1))
{
//选择最小的子节点交换
if (heap[left] < heap[right])
{
swap(bubble, left);
bubble = left;
right = bubble * 2 + 2;
left = bubble * 2 + 1;
continue;
}
else
{
swap(bubble, right);
bubble = right;
right = bubble * 2 + 2;
left = bubble * 2 + 1;
continue;
}
}
return temp;
}
int main()
{
MinHeap<int> heap;
heap.insertItem(11);
heap.insertItem(10);
heap.insertItem(91);
heap.insertItem(15);
heap.insertItem(25);
heap.insertItem(13);
heap.insertItem(12);
heap.insertItem(92);
heap.insertItem(80);
while (!heap.isEmpty())
std::cout << heap.removeMin() << std::endl;
}
4 哈希表
4.1链表法
#include <iostream>
#include <string>
//哈希表(也叫哈希映射,散列表)是一种基本将键映射到值的数据结构。//若关键字为k,则其值存放在f(k)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数,按这个思想建立的表为散列表。
//对不同的关键字可能得到同一散列地址,即k1!=k2,而f(k1)=f(k2),这种现象称为冲突(英语:Collision)。
//常见的哈希函数 直接定址法 除留余数法,平方取中法
//处理哈希冲突的方法:1.拉链法(链表法)2.开放寻址法,闭散列法3.再散列法
//线性探测法、二次探测法都属于开放地址法
//本文采用链表法
template <typename K, typename V>
struct HashNode
{
HashNode<K, V>* next;
K key;
V value;
HashNode(K k, V v) : next(nullptr), key(k), value(v) {};
};
template <typename K, typename V>
class HashTable
{
private:
HashNode<K, V>** table;
int capacity;
int hashFunc(K key); //返回索引
public:
HashTable(int cap);
~HashTable();
void insertItem(K key, V value);
void deleteItem(K key);
bool getValue(const K& key, V& value);
void print()
{
std::cout << "table\t" << std::endl;
for (int i = 0; i < capacity; i++)
{
if (table[i] != nullptr)
{
HashNode<K, V>* curr = table[i];
std::cout << i << ": ";
while (curr != nullptr)
{
std::cout << "(" << curr->key << "," << curr->value <<")"<< "--->";
curr = curr->next;
}
std::cout << std::endl;
}
else
{
std::cout << i << ": \n";
}
}
}
};
template <typename K, typename V>
HashTable<K, V>::HashTable(int cap)
{
capacity = cap;
table = new HashNode<K, V> * [cap];
for (int i = 0; i < cap; i++)
{
table[i] = nullptr;
}
};
template <typename K, typename V>
HashTable<K, V>::~HashTable()
{
//释放链表内存
for (int i = 0; i < capacity; i++)
{
HashNode<K, V>* ptr = table[i];
while (ptr != nullptr)
{
HashNode<K, V>* temp = ptr;
ptr = ptr->next;
delete temp;
}
table[i] = nullptr;
}
//删除整个表
delete[] table;
};
template <typename K, typename V>
int HashTable<K, V>::hashFunc(K key)
{
return key % capacity;
};
template <typename K, typename V>
void HashTable<K, V>::insertItem(K key, V value)
{
int index = hashFunc(key);
HashNode<K, V>* curr = table[index];
if (curr == nullptr)
{
table[index] = new HashNode<K, V>(key, value);
return;
}
else
{
HashNode<K, V>* prev = nullptr;
while (curr != nullptr && curr->key != key)
{
prev = curr;
curr = curr->next;
}
if (curr == nullptr)
{
prev->next = new HashNode<K, V>(key, value);
return;
}
else
{
curr->value = value; //已有相同的key则更新value值
return;
}
}
};
template <typename K, typename V>
void HashTable<K, V>::deleteItem(K key)
{
int index = hashFunc(key);
HashNode<K, V>* curr = table[index];
if (curr == nullptr)
{
std::cout << "empty hashtable";
return;
}
else
{
HashNode<K, V>* prev = nullptr;
while (curr != nullptr && curr->key != key)
{
prev = curr;
curr = curr->next;
}
if (curr == nullptr)
{
std::cout << "key not find " << std::endl;
return;
}
else
{
//当删除链表头
if (prev == nullptr)
{
//若链表长度大于1
if (curr->next != nullptr)
table[index] = curr->next; //将链表头移动
else
{
delete curr;
table[index] = nullptr;
return;
}
}
else
{
prev->next = curr->next;
}
delete curr;
return;
}
}
}
template <typename K, typename V>
bool HashTable<K, V>::getValue(const K& key, V& value)
{
int index = hashFunc(key);
HashNode<K, V>* curr = table[index];
if (table[index] == nullptr)
{
return false;
}
while (curr->key != key && curr != nullptr)
{
curr = curr->next;
}
if (curr == nullptr)
{
return false;
}
else
{
value = curr->value;
return true;
}
}
#define TABLE_SIZE 5
int main()
{
printf("hello hash table\n");
HashTable<int, std::string> mytable(TABLE_SIZE);
mytable.insertItem(2, "DD");
mytable.insertItem(107, "BA");
mytable.insertItem(17, "fffA");
mytable.insertItem(178, "sasad");
mytable.insertItem(255, "sfd");
mytable.insertItem(498, "ssdasdf");
mytable.insertItem(193, "sf");
mytable.insertItem(105, "sf");
mytable.insertItem(44, "yyty");
mytable.print();
mytable.deleteItem(255);
mytable.deleteItem(105);
std::string result;
if (mytable.getValue(498, result))
std::cout << "498: " << result << std::endl;
//58 17 466 471 376 255 491 178 147 498
mytable.print();
}
相关leetcode:寻找两数之和为给定值的数组下标leetcode-cn.com/problems/tw…
💡:stl中,map是用红黑树实现的,查找需要O(log n),而std::unordered_map,则是通过哈希表实现的,查找需要O(1)。
#include<unordered_map>
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
std::unordered_map<int,int> hashmap;//
vector<int> indices(2,-1);
for(int i=nums.size()-1;i>=0;i--)
{
if(hashmap.count(target-nums[i])>0)
{
indices[0]=hashmap[target-nums[i]];
indices[1]=i;
break;
}
hashmap[nums[i]]=i;
}
return indices;
}
};
4.2 开放寻址法
5 图与相关算法
5.1 图介绍
图有两个部分组成:
- 顶点vertice(一个顶点)也称为节点。
- 边edge。无向图具有无向边。称两个相连的节点为neighbor,如果给图的每条边规定一个方向,那么得到的图称为有向图,其边也称为有向边。在有向图中,与一个节点相关联的边有出边和入边之分,而与一个有向边关联的两个点也有始点和终点之分。相反,边没有方向的图称为无向图。如下图所示
在数学上,图可以表示为G=(V,E),V代表顶点集,E代表边集。
5.2 图的相关概念
- 路径path:从一个顶点到另一个顶点所经过的顶点与边的集合
- 环loop:起点与终点相同的路径
连通:如果存在从每个顶点到每个其他顶点的路径,则图是连通的(有向图是强连通的)
- 度(Degree):一个顶点的度是指与该顶点相联的总边数
- 出度(Out-degree)和入度(In-degree):对有向图而言,顶点的度还可分为出度和入度。一个顶点的出度为d0,是指有d0条边以该顶点为起点,或说与该点关联的出边共有d0条。入度的概念也类似。
5.3 图的种类
- 连通图 强连通图 如果一个无向图的每个顶点都可以从其他顶点到达,那么这个无向图就是连通的。如果一个有向图的每个顶点都可以从其他顶点到达,那么这个有向图就是强连通的。
- 有向图与无向图
- 有权重与无权重
- 简单图与非简单图(区别在于有没有自环,若一条边的两个顶点相同,则此边称作自环。)
- 稀疏(sparse)与密集(dense)
图可以具有二次数量的边。 如果V是图形中的顶点数,则最多可以具有O(V2)个边。 接近O(V^2)条边这种图称为密集图。 另一方面,具有较少数量的边的图被称为稀疏图。 如果图在每对节点之间都有一条边,我们将此图称为完全图。 下图展示了一个稀疏图与密集图。
- 有环与无环
The directed acyclic graph (DAG) is applied to the parallel multiprocessor system to present the dependence relationship between tasks.
在并行多处理器系统中,通常用有向无环图(DAG)表示任务之间的依赖关系。没有环的图是无环图。树是一个连通的无环图。有向无环图(DAG)在计算机科学中有很多用例,包括任务调度问题。拓扑排序等调度算法要求图是一个DAG。下图给出了有环图和无环图的例子。
5.4 图的表示方法
有如下图所示的图:
1.邻接矩阵
一些复杂度:
c++用一个二维数组实现(用到了DFS深度优先遍历,以后深入来讲)
#include <iostream>
#include <vector>
//邻接矩阵法表示图(adjacency matrix)
//邻接矩阵是一个二维数组
//若两个顶点u,v之间存在边,adj_matrix[u][v]=true
//反之为false
//邻接矩阵只适用于没有重边(或重边可以忽略)的情况。
//邻接矩阵其最显著的优点是可以 查询一条边是否存在。
//由于邻接矩阵在稀疏图上效率很低(尤其是在点数较多的图上,空间无法承受),所以一般只会在稠密图上使用邻接矩阵。
int n, m; //假设有n个顶点 m条边
std::vector<std::vector<bool>> adj_matrix; //储存矩阵的二维数组
std::vector<bool> visit; //标记访问过的节点
//深度优先搜索法是树的先根遍历的推广,它的基本思想是:从图G的某个顶点v0出发,访问v0,然后选择一个与v0相邻且没被访问过的顶点vi访问,
//再从vi出发选择一个与vi相邻且未被访问的顶点vj进行访问,依次继续。如果当前被访问过的顶点的所有邻接顶点都已被访问,则退回到已被访问的顶点序列中最后一个拥有未被访问的相邻顶点的顶点w,从w出发按同样的方法向前遍历,直到图中所有顶点都被访问。
void DFStraverse(int v)
{
//若已经访问过 直接返回
if (visit[v])
return;
//标记访问的顶点
visit[v] = true;
std::cout << v << "\t";
for (int i = 0; i < n; i++)
{
if (adj_matrix[v][i])
{
DFStraverse(i);
}
}
}
void printAdjMatrix(std::vector<std::vector<bool>> &adj_matrix)
{
for (auto line : adj_matrix)
{
std::cout << "\n";
for (auto elem : line)
{
std::cout << elem << "\t";
}
}
};
void initialize()
{
int u, v;
std::cout << "input the number of vertices\n";
std::cin >> n;
std::cout << "input the number of edges\n";
std::cin >> m;
adj_matrix.resize(n, std::vector<bool>(n, false)); //n+1行,每一行都是false组成的大小为n+1的数组
visit.resize(n, false);
//有向图的初始化
for (int i = 0; i < m; i++)
{
std::cin >> u >> v;
if (u <= n - 1 && v <= n - 1 && u >= 0 && v >= 0)
adj_matrix[u][v] = true;
else
{
std::cout << "invalid u,v";
system("pause");
exit(0);
}
}
}
int main()
{
initialize();
printAdjMatrix(adj_matrix);
std::cout << "\nDFS TRAVERSE pls input start vertice\n";
int source = 0; //DFS起始点
std::cin >> source;
DFStraverse(source);
}
输出结果:
input the number of vertices
4
input the number of edges
6
0 1
0 2
1 2
2 0
2 3
3 3
0 1 1 0
0 0 1 0
1 0 0 1
0 0 0 1
DFS TRAVERSE pls input start vertice
1
1 2 0 3
2.邻接表
c++实现代码:
#include <iostream>
#include <list>
#include <vector>
class Graph
{
private:
int V; //number of vertices
std::list<int> *adjList; //在这里邻接表是一个由链表组成的数组
//邻接表也可以用哈希表或者动态数组实现
public:
Graph(int v) : V(v)
{
adjList = new std::list<int>[v];
};
void addEdge(int u, int v)
{
adjList[u].push_back(v);
}
void DFSutil(std::vector<bool> &visit, int v)
{
std::cout << v << "\t";
visit[v] = true;
for (std::list<int>::iterator it = adjList[v].begin(); it != adjList[v].end(); ++it)
{
//*it 指向一个list中的顶点编号,若未访问过,则递归调用DFSutil访问邻居顶点
if (!visit[*it])
DFSutil(visit, *it);
}
}
void DFS(int v)
{
if (v >= V)
{
std::cout << "invalid start vertex" << std::endl;
return;
}
std::vector<bool> visit; //记录访问过的节点
visit.resize(V, false);
DFSutil(visit, v);
}
};
int main()
{
std::cout << "Graph implementation in adjacent list\n";
Graph g(4);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
std::cout << "Following is Depth First Traversal"
" (starting from vertex 2) \n";
g.DFS(2);
// 时间复杂度:O(V + E),其中V是顶点数量,E是图形中边的数量。
// 空间复杂度:O(V)。
// 由于需要一个额外的访问数组记录访问过的节点,其大小为V。
}
一些复杂度:(n为顶点数,m为边的数目)
5.5 有向无环图DAG的拓扑排序
计算机系统中的许多任务