#include <iostream>
#include <cstring>
using namespace std;
class Vector {
private:int size, length;
int *data;
public:
Vector(int input_size) {
size = input_size;
length = 0;
data = new int[size];
}
~Vector() {
delete[] data;
}
};
int main() {
Vector a(100);
return 0;
}
插入:
#include <iostream>
#include <cstring>
using namespace std;
class Vector {
private:
int size, length;
int *data;
public:
Vector(int input_size) {
size = input_size;
length = 0;
data = new int[size];
}
~Vector() {
delete[] data;
}
bool insert(int loc, int value) {
if (loc < 0 || loc > length) {
return false;
}
if (length >= size) {
return false;
}
for(int i = length; i > loc; i--) {
data[i] = data[i - 1];
}
data[loc] = value;
length++;
return true;
}
};
int main() {
Vector a(2);
cout << a.insert(1, 0) << endl;
cout << a.insert(0, 1) << endl;
cout << a.insert(2, 1) << endl;
cout << a.insert(1, 2) << endl;
cout << a.insert(0, 3) << endl;
return 0;
}
扩容:
#include <iostream>
#include <cstring>
using namespace std;
class Vector {
private:
int size, length;
int *data;
public:
Vector(int input_size) {
size = input_size;
length = 0;
data = new int[size];
}
~Vector() {
delete[] data;
}
bool insert(int loc, int value) {
if (loc < 0 || loc > length) {
return false;
}
if (length >= size) {
//return false;
expand();
}
for (int i = length; i > loc; --i) {
data[i] = data[i - 1];
}
data[loc] = value;
length++;
return true;
}
void expand() {
int *old_data = data;
size = size * 2;
data = new int[size];
for(int i = 0; i < length; i++) {
data[i] = old_data[i];
}
delete[] old_data;
}
};
int main() {
Vector a(2);
cout << a.insert(1, 0) << endl;
cout << a.insert(0, 1) << endl;
cout << a.insert(2, 1) << endl;
cout << a.insert(1, 2) << endl;
cout << a.insert(0, 3) << endl;
return 0;
}
查找:
#include <iostream>
#include <cstring>
using namespace std;
class Vector {
private:
int size, length;
int *data;
public:
Vector(int input_size) {
size = input_size;
length = 0;
data = new int[size];
}
~Vector() {
delete[] data;
}
bool insert(int loc, int value) {
if (loc < 0 || loc > length) {
return false;
}
if (length >= size) {
return false;
}
for (int i = length; i > loc; --i) {
data[i] = data[i - 1];
}
data[loc] = value;
length++;
return true;
}
int search(int value) {
for(int i = 0; i < length; i++) {
if(data[i] == value) {
return i;
}
}
return -1;
}
};
int main() {
Vector a(2);
cout << a.insert(1, 0) << endl;
cout << a.insert(0, 1) << endl;
cout << a.insert(2, 1) << endl;
cout << a.insert(1, 2) << endl;
cout << a.insert(0, 3) << endl;
cout << a.search(1) << endl;
cout << a.search(4) << endl;
return 0;
}
删除:
#include <iostream>
#include <cstring>
using namespace std;
class Vector {
private:
int size, length;
int *data;
public:
Vector(int input_size) {
size = input_size;
length = 0;
data = new int[size];
}
~Vector() {
delete[] data;
}
bool insert(int loc, int value) {
if (loc < 0 || loc > length) {
return false;
}
if (length >= size) {
return false;
}
for (int i = length; i > loc; --i) {
data[i] = data[i - 1];
}
data[loc] = value;
length++;
return true;
}
int search(int value) {
for (int i = 0; i < length; ++i) {
if (data[i] == value) {
return i;
}
}
return -1;
}
bool remove(int index) {
if(index < 0 || index >= length) {
return false;
}
for(int i = index + 1; i < length; i++) {
data[i - 1] = data[i];
}
length--;
return true;
}
};
int main() {
Vector a(2);
cout << a.insert(0, 1) << endl;
cout << a.insert(0, 2) << endl;
cout << a.remove(1) << endl;
cout << a.search(0) << endl;
cout << a.search(1) << endl;
return 0;
}
遍历:
#include <iostream>
#include <cstring>
using namespace std;
class Vector {
private:
int size, length;
int *data;
public:
Vector(int input_size) {
size = input_size;
length = 0;
data = new int[size];
}
~Vector() {
delete[] data;
}
bool insert(int loc, int value) {
if (loc < 0 || loc > length) {
return false;
}
if (length >= size) {
return false;
}
for (int i = length; i > loc; --i) {
data[i] = data[i - 1];
}
data[loc] = value;
length++;
return true;
}
int search(int value) {
for (int i = 0; i < length; ++i) {
if (data[i] == value) {
return i;
}
}
return -1;
}
bool remove(int index) {
if (index < 0 || index >= length) {
return false;
}
for (int i = index + 1; i < length; ++i) {
data[i - 1] = data[i];
}
length = length - 1;
return true;
}
void print() {
for(int i = 0; i < length; i++) {
if(i > 0) {
cout << " ";
}
cout << data[i];
}
cout << endl;
}
};
int main() {
Vector a(2);
cout << a.insert(0, 1) << endl;
cout << a.insert(0, 2) << endl;
a.print();
cout << a.remove(1) << endl;
a.print();
cout << a.search(0) << endl;
cout << a.search(1) << endl;
return 0;
}
顺序表循环左移:
#include <iostream>
#include <cstring>
using namespace std;
class Vector {
private:
int size, length;
int *data;
public:
Vector(int input_size) {
size = input_size;
length = 0;
data = new int[size];
}
~Vector() {
delete[] data;
}
bool insert(int loc, int value) {
if (loc < 0 || loc > length) {
return false;
}
if (length >= size) {
return false;
}
for (int i = length; i > loc; --i) {
data[i] = data[i - 1];
}
data[loc] = value;
length++;
return true;
}
int search(int value) {
for (int i = 0; i < length; ++i) {
if (data[i] == value) {
return i;
}
}
return -1;
}
bool remove(int index) {
if (index < 0 || index >= length) {
return false;
}
for (int i = index + 1; i < length; ++i) {
data[i - 1] = data[i];
}
length = length - 1;
return true;
}
void print() {
for(int i = 0; i < length; i++)
{
if(i > 0)
{
cout << " ";
}
cout << data[i];
}
cout << endl;
}
int get_data(int index)
{
if(index < 0 || index >= length)
return -1;
else{
return data[index];
}
}
};
int main() {
int m, k, num;
scanf("%d %d",&m,&k);
Vector a(m);
for(int i = 0; i < m; i++)
{
scanf("%d",&num);
a.insert(i, num);
}
for(int i = 0; i < k; i++)
{
int temp = a.get_data(0);
a.remove(0);
a.insert(m - 1,temp);
}
a.print();
return 0;
}
链表:
创建和插入:
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node* next;
Node(int _data) {
data = _data;
next = NULL;
}
};
class LinkList {
private:
Node* head;
public:
LinkList() {
head = NULL;
}
void insert(Node* node, int index) {
if(head == NULL) {
head = node;
return;
}
if(index == 0) {
node->next = head;
head = node;
return;
}
Node* current_node = head;
int count = 0;
while(current_node->next != NULL && count < index - 1) {
current_node = current_node->next;
count++;
}
if(count == index - 1){
node->next = current_node->next;
current_node->next = node;
}
}
};
int main() {
LinkList linklist;
for(int i = 1; i <=10; i++) {
Node* node = new Node(i);
linklist.insert(node, i - 1);
}
return 0;
}
遍历:
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node* next;
Node(int _data) {
data = _data;
next = NULL;
}
};
class LinkList {
private:
Node* head;
public:
LinkList() {
head = NULL;
}
void insert(Node *node, int index) {
if (head == NULL) {
head = node;
return;
}
if (index == 0) {
node->next = head;
head = node;
return;
}
Node *current_node = head;
int count = 0;
while (current_node->next != NULL && count < index - 1) {
current_node = current_node->next;
count++;
}
if (count == index - 1) {
node->next = current_node->next;
current_node->next = node;
}
}
void output() {
if(head == NULL) {
return;
}
Node* current_node = head;
while(current_node != NULL) {
cout << current_node->data << " ";
current_node = current_node->next;
}
cout << endl;
}
};
int main() {
LinkList linklist;
for (int i = 1; i <= 10; i++) {
Node *node = new Node(i);
linklist.insert(node, i - 1);
}
linklist.output();
return 0;
}
删除:
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node* next;
Node(int _data) {
data = _data;
next = NULL;
}
};
class LinkList {
private:
Node* head;
public:
LinkList() {
head = NULL;
}
void insert(Node *node, int index) {
if (head == NULL) {
head = node;
return;
}
if (index == 0) {
node->next = head;
head = node;
return;
}
Node *current_node = head;
int count = 0;
while (current_node->next != NULL && count < index - 1) {
current_node = current_node->next;
count++;
}
if (count == index - 1) {
node->next = current_node->next;
current_node->next = node;
}
}
void output() {
if (head == NULL) {
return;
}
Node *current_node = head;
while (current_node != NULL) {
cout << current_node->data << " ";
current_node = current_node->next;
}
cout << endl;
}
void delete_node(int index) {
if(head == NULL) {
return;
}
Node* current_node = head;
int count = 0;
if(index == 0) {
head = head->next;
delete current_node;
return;
}
while(current_node->next != NULL && count < index - 1) {
current_node = current_node->next;
count++;
}
if(count == index - 1 && current_node->next != NULL) {
Node* delete_node = current_node->next;
current_node->next = delete_node->next;
delete delete_node;
}
}
};
int main() {
LinkList linklist;
for (int i = 1; i <= 10; i++) {
Node *node = new Node(i);
linklist.insert(node, i - 1);
}
linklist.output();
linklist.delete_node(0);
linklist.output();
return 0;
}
反转(掉头):
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node* next;
Node(int _data) {
data = _data;
next = NULL;
}
};
class LinkList {
private:
Node* head;
public:
LinkList() {
head = NULL;
}
void insert(Node *node, int index) {
if (head == NULL) {
head = node;
return;
}
if (index == 0) {
node->next = head;
head = node;
return;
}
Node *current_node = head;
int count = 0;
while (current_node->next != NULL && count < index - 1) {
current_node = current_node->next;
count++;
}
if (count == index - 1) {
node->next = current_node->next;
current_node->next = node;
}
}
void output() {
if (head == NULL) {
return;
}
Node *current_node = head;
while (current_node != NULL) {
cout << current_node->data << " ";
current_node = current_node->next;
}
cout << endl;
}
void delete_node(int index) {
if (head == NULL) {
return;
}
Node *current_node = head;
int count = 0;
if (index == 0) {
head = head->next;
delete current_node;
return;
}
while (current_node->next != NULL && count < index -1) {
current_node = current_node->next;
count++;
}
if (count == index - 1 && current_node->next != NULL) {
Node *delete_node = current_node->next;
current_node->next = delete_node->next;
delete delete_node;
}
}
void reverse() {
if(head == NULL) {
return;
}
Node *next_node, *current_node;
current_node = head->next;
head->next = NULL;
while(current_node != NULL) {
next_node = current_node->next;
current_node->next = head;
head = current_node;
current_node = next_node;
}
}
};
int main() {
LinkList linklist;
for (int i = 1; i <= 10; i++) {
Node *node = new Node(i);
linklist.insert(node, i - 1);
}
linklist.output();
linklist.delete_node(3);
linklist.output();
linklist.reverse();
linklist.output();
return 0;
}
约瑟夫环:
创建:
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node* next;
Node(int _data) {
data = _data;
next = NULL;
}
};
class LinkList {
private:
Node* head;
public:
LinkList() {
head = NULL;
}
void insert(Node *node, int index) {
if (head == NULL) {
head = node;
head->next = head;
return;
}
if (index == 0) {
node->next = head->next;
head->next = node;
return;
}
Node *current_node = head->next;
int count = 0;
while (current_node != head && count < index - 1) {
current_node = current_node->next;
count++;
}
if (count == index - 1) {
node->next = current_node->next;
current_node->next = node;
}
if (node == head->next) {
head = node;
}
}
};
int main() {
LinkList linklist;
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
Node *node = new Node(i);
linklist.insert(node, i - 1);
}
return 0;
}
删除环上元素:
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node* next;
Node(int _data) {
data = _data;
next = NULL;
}
};
class LinkList {
private:
Node* head;
public:
LinkList() {
head = NULL;
}
void insert(Node *node, int index) {
if (head == NULL) {
head = node;
head->next = head;
return;
}
if (index == 0) {
node->next = head->next;
head->next = node;
return;
}
Node *current_node = head->next;
int count = 0;
while (current_node != head && count < index - 1) {
current_node = current_node->next;
count++;
}
if (count == index - 1) {
node->next = current_node->next;
current_node->next = node;
}
if (node == head->next) {
head = node;
}
}
void output_josephus(int m) {
Node *current_node = head;
while(current_node->next != current_node) {
for(int i = 1; i < m; i++) {
current_node = current_node->next;
}
cout << current_node->next->data << " ";
Node *delete_node = current_node->next;
current_node->next = current_node->next->next;
delete delete_node;
}
cout << current_node->data << endl;
delete current_node;
}
};
int main() {
LinkList linklist;
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
Node *node = new Node(i);
linklist.insert(node, i - 1);
}
linklist.output_josephus(m);
return 0;
}
队列:
创建,定义:#include <iostream>
using namespace std;
class Queue {
private:int *data;
int head, tail, length;
public:Queue(int length_input) {
data = new int[length_input];
length = length_input;
head = 0;
tail = -1;
}
~Queue() {
delete[] data;
}
};
int main() {
Queue queue(100);
return 0;
}
插入:
#include <iostream>
using namespace std;
class Queue {
private:
int *data;
int head, tail, length;
public:
Queue(int length_input) {
data = new int[length_input];
length = length_input;
head = 0;
tail = -1;
}
~Queue() {
delete[] data;
}
void push(int element) {
if(tail + 1 < length) {
tail++;
data[tail] = element;
}
}
};
int main() {
Queue queue(100);
for(int i = 1; i <= 10; i++) {
queue.push(i);
}
return 0;
}
输出:
#include <iostream>
using namespace std;
class Queue {
private:
int *data;
int head, tail, length;
public:
Queue(int length_input) {
data = new int[length_input];
length = length_input;
head = 0;
tail = -1;
}
~Queue() {
delete[] data;
}
void push(int element) {
if (tail + 1 < length) {
tail++;
data[tail] = element;
}
}
void output() {
for(int i = head; i <= tail; i++) {
cout << data[i] << " ";
}
cout << endl;
}
};
int main() {
Queue queue(100);
for (int i = 1; i <= 10; i++) {
queue.push(i);
}
queue.output();
return 0;
}
队首元素的输出和删除:
#include <iostream>
#include <cassert>
using namespace std;
class Queue {
private:
int *data;
int head, tail, length;
public:
Queue(int length_input) {
data = new int[length_input];
length = length_input;
head = 0;
tail = -1;
}
~Queue() {
delete[] data;
}
void push(int element) {
if (tail + 1 < length) {
tail++;
data[tail] = element;
}
}
void output() {
for (int i = head; i <= tail; i++) {
cout << data[i] << " ";
}
cout << endl;
}
int front() {
assert(head <= tail);
return data[head];
}
void pop() {
assert(head <= tail);
head++;
}
};
int main() {
Queue queue(100);
for (int i = 1; i <= 10; i++) {
queue.push(i);
}
queue.output();
cout << queue.front() <<endl;
queue.pop();
queue.output();
return 0;
}
循环队列:
插入:
#include <iostream>
#include <cassert>
using namespace std;
class Queue {
private:
int *data;
int head, tail, length, count;
public:
Queue(int length_input) {
data = new int[length_input];
length = length_input;
head = 0;
tail = -1;
count = 0;
}
~Queue() {
delete[] data;
}
队首元素查询和出队:
#include <iostream>
#include <cassert>
using namespace std;
class Queue {
private:
int *data;
int head, tail, length, count;
public:
Queue(int length_input) {
data = new int[length_input];
length = length_input;
head = 0;
tail = -1;
count = 0;
}
~Queue() {
delete[] data;
}
bool push(int element) {
if (count < length) {
tail = (tail + 1) % length;
data[tail] = element;
count++;
return true;
} else {
return false;
}
}
void output() {
for (int i = head; i != tail + 1; i = (i + 1) % length) {
cout << data[i] << " ";
}
cout << endl;
}
int front() {
assert(count > 0);
return data[head];
}
void pop() {
assert(count > 0);
head = (head + 1) % length;
count--;
}
};
int main() {
Queue queue(100);
for (int i = 1; i <= 10; i++) {
queue.push(i);
}
queue.output();
cout << queue.front() << endl;
queue.pop();
queue.output();
return 0;
}
void push(int element) {
if (count < length) {
tail = (tail + 1) % length;
data[tail] = element;
count++;
}
}
void output() {
for (int i = head; i != tail + 1; i = (i + 1) % length) {
cout << data[i] << " ";
}
cout << endl;
}
};
int main() {
Queue queue(100);
for (int i = 1; i <= 10; i++) {
queue.push(i);
}
queue.output();
return 0;
}
栈:
创建:
#include<iostream>
#include<string>
using namespace std;
template<class Type> class Stack {
private:Type *urls;
int max_size, top_index;
public:Stack(int length_input) {
urls = new Type[length_input];
max_size = length_input;
top_index = -1;
}
~Stack() {
delete[] urls;
}
};
int main() {
int n;
cin >> n;
Stack<string> stack(n);
return 0;
}
插入:
#include<iostream>
#include<string>
using namespace std;
template<class Type> class Stack {
private:
Type *urls;
int max_size, top_index;
public:
Stack(int length_input) {
urls = new Type[length_input];
max_size = length_input;
top_index = -1;
}
~Stack() {
delete[] urls;
}
bool push(const Type &element) {
if(top_index >= max_size - 1) {
return false;
}
top_index++;
urls[top_index] = element;
return true;
}
};
int main() {
int n, m;
cin >> n >> m;
Stack<string> stack(n);
for(int i = 1; i <= m; i++) {
int opr;
cin >> opr;
if(opr == 0) {
string element;
cin >> element;
if(stack.push(element)) {
cout << "push success!" << endl;
}
else {
cout << "push failed!" << endl;
}
}
}
return 0;
}
删除:
#include<iostream>
#include<string>
using namespace std;
template<class Type> class Stack {
private:
Type *urls;
int max_size, top_index;
public:
Stack(int length_input) {
urls = new Type[length_input];
max_size = length_input;
top_index = -1;
}
~Stack() {
delete[] urls;
}
bool push(const Type &element) {
if (top_index >= max_size - 1) {
return false;
}
top_index++;
urls[top_index] = element;
return true;
}
bool pop() {
if(top_index < 0) {
return false;
}
top_index--;
return true;
}
};
int main() {
int n, m;
cin >> n >> m;
Stack<string> stack(n);
for (int i = 1; i <= m; i++) {
int opr;
cin >> opr;
if (opr == 0) {
string element;
cin >> element;
if (stack.push(element)) {
cout << "push success!" << endl;
} else {
cout << "push failed!" << endl;
}
}
else if(opr == 1) {
if(stack.pop()) {
cout << "pop success!" << endl;
}
else {
cout << "pop failed!" <<endl;
}
}
}
return 0;
}
访问栈顶元素:
#include<iostream>
#include<string>
#include<cassert>
using namespace std;
template<class Type> class Stack {
private:
Type *urls;
int max_size, top_index;
public:
Stack(int length_input) {
urls = new Type[length_input];
max_size = length_input;
top_index = -1;
}
~Stack() {
delete[] urls;
}
bool push(const Type &element) {
if (top_index >= max_size - 1) {
return false;
}
top_index++;
urls[top_index] = element;
return true;
}
bool pop() {
if (top_index < 0) {
return false;
}
top_index--;
return true;
}
Type top() {
assert(top_index >= 0);
return urls[top_index];
}
};
int main() {
int n, m;
cin >> n >> m;
Stack<string> stack(n);
for (int i = 1; i <= m; i++) {
int opr;
cin >> opr;
if (opr == 0) {
string element;
cin >> element;
if (stack.push(element)) {
cout << "push success!" << endl;
} else {
cout << "push failed!" << endl;
}
} else if (opr == 1) {
if (stack.pop()) {
cout << "pop success!" << endl;
} else {
cout << "pop failed!" << endl;
}
}
else if(opr == 2) {
cout << stack.top() << endl;
}
}
return 0;
}
利用栈实现数列反转:
#include<iostream>
#include<string>
#include<cassert>
using namespace std;
template<class Type> class Stack {
private:
Type *urls;
int max_size, top_index;
public:
Stack(int length_input) {
urls = new Type[length_input];
max_size = length_input;
top_index = -1;
}
~Stack() {
delete[] urls;
}
bool push(const Type &element) {
if (top_index >= max_size - 1) {
return false;
}
top_index++;
urls[top_index] = element;
return true;
}
bool pop() {
if (top_index < 0) {
return false;
}
top_index--;
return true;
}
Type top() {
assert(top_index >= 0);
return urls[top_index];
}
bool empty() {
if(top_index < 0) {
return true;
}
else {
return false;
}
}
};
int main() {
int n, num;
cin >> n;
Stack<int> stack(n);
for(int i = 1; i <= n; i++) {
cin >> num;
stack.push(num);
}
while(!stack.empty()) {
cout << stack.top() << " ";
stack.pop();
}
return 0;
}
用栈实现 + * 表达式运算:
#include<iostream>
#include<string>
#include<cassert>
using namespace std;
template<class Type> class Stack {
private:
Type *urls;
int max_size, top_index;
public:
Stack(int length_input) {
urls = new Type[length_input];
max_size = length_input;
top_index = -1;
}
~Stack() {
delete[] urls;
}
bool push(const Type &element) {
if (top_index >= max_size - 1) {
return false;
}
top_index++;
urls[top_index] = element;
return true;
}
bool pop() {
if (top_index < 0) {
return false;
}
top_index--;
return true;
}
Type top() {
assert(top_index >= 0);
return urls[top_index];
}
bool empty() {
if (top_index < 0) {
return true;
} else {
return false;
}
}
};
bool precede(char a, char b) {
if (a == '*') {
return true;
} else {
return false;
}
}
int operate(char theta, int a, int b) {
if (theta == '+') {
return a + b;
} else {
return a * b;
}
}
void calc(Stack<int> &numbers, Stack<char> &operators) {
int a = numbers.top();
numbers.pop();
int b = numbers.top();
numbers.pop();
numbers.push(operate(operators.top(), a, b));
operators.pop();
}
int main() {
int n;
cin >> n;
Stack<int> numbers(n);
Stack<char> operators(n);
string buffer;
cin >> buffer;
int i = 0;
while(i < n) {
if(isdigit(buffer[i])) {
numbers.push(buffer[i] - '0');
i++;
}
else {
if(operators.empty() || precede(buffer[i], operators.top())) {
operators.push(buffer[i]);
i++;
}
else {
calc(numbers, operators);
}
}
}
while(!operators.empty()) {
calc(numbers, operators);
}
cout << numbers.top() << endl;
return 0;
}
单调栈木板问题:
#include<iostream>
#include<cassert>
using namespace std;
class Node {
public:
int id, height;
};
template<class Type> class Stack {
private:
Type *urls;
int max_size, top_index;
public:
Stack(int length_input) {
urls = new Type[length_input];
max_size = length_input;
top_index = -1;
}
~Stack() {
delete[] urls;
}
bool push(const Type &element) {
if (top_index >= max_size - 1) {
return false;
}
top_index++;
urls[top_index] = element;
return true;
}
bool pop() {
if (top_index < 0) {
return false;
}
top_index--;
return true;
}
Type top() {
assert(top_index >= 0);
return urls[top_index];
}
bool empty() {
if (top_index < 0) {
return true;
} else {
return false;
}
}
};
int main() {
int n, ans = 0;
cin >> n;
Stack<Node> stack(n);
Node temp;
for(int i = 1; i <=n ; i++) {
cin >> temp.height;
temp.id = i;
while(!stack.empty() && stack.top().height <= temp.height) {
ans += i - stack.top().id - 1;
stack.pop();
}
stack.push(temp);
}
while(!stack.empty()) {
ans += n + 1 - stack.top().id - 1;// ??????????????
stack.pop();
}
cout << ans << endl;
return 0;
}
/*
*/
栈能否输出指定数列:
#include<iostream>
#include<string>
#include<cassert>
using namespace std;
template<class Type> class Stack {
private:
Type *urls;
int max_size, top_index;
public:
Stack(int length_input) {
urls = new Type[length_input];
max_size = length_input;
top_index = -1;
}
~Stack() {
delete[] urls;
}
bool push(const Type &element) {
if (top_index >= max_size - 1) {
return false;
}
top_index++;
urls[top_index] = element;
return true;
}
bool pop() {
if (top_index < 0) {
return false;
}
top_index--;
return true;
}
Type top() {
assert(top_index >= 0);
return urls[top_index];
}
bool empty() {
if(top_index < 0) {
return true;
}
else {
return false;
}
}
};
int main() {
int N;
cin >> N;
int a[N];
Stack<int> stacka(N);
Stack<int> stackn(N);
for(int i = 0; i < N; i++) {
cin >> a[i];
}
for(int i = N - 1; i > -1; i--) {
stacka.push(a[i]);
}
for(int i = 1; i <= N; i++) {
stackn.push(i);
while(stackn.top() == stacka.top()) {
stacka.pop();
stackn.pop();
if(stackn.empty()) {
break;
}
}
}
if(stacka.empty()) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
return 0;
}
/*
*/
哈希表:
创建:
#include <iostream>
#include <string>
using namespace std;
class HashTable {
private:string *elem;
int size;
public:HashTable() {
size = 2000;
elem = new string[size];
for(int i = 0; i < size; i++) {
elem[i] = "#";
}
}
~HashTable() {
delete[] elem;
}
};
int main() {
HashTable hashtable;
return 0;
}
常见的哈希函数构造方法有哪些呢?
直接寻址法。我们取关键字的值或者关键字的某个函数变换值,线性的映射到存储地址上。如果关键字的数量和跨
度不是很大,直接寻址法是最简单最有效的构造方法,并且可以避免冲突。但是如果关键字的数量和跨度很大的话
,这种方法就用不了了,例如有 n个关键字,值最小的为 0,最大的为 10^{10}
,这种情况下就不能用直接寻址法了,因为没有足够的空间可用来存储。
除留余数法。我们将关键字对整数 p 取的余数直接做为存储地址,整数 p 一般取小于等于哈希表长度
size 的质数,如果关键字不是整数,比如是个字符串,可以先将其做个转换,然后再对 pp 取余。选择优秀
的 p 可以避免冲突。其他的构造方法还有分析数字法,随机数法等等。
设计哈希函数没有统一的方法,同一个哈希函数不一定能适用所有问题,其产生的影响也是不一样的。哈希函数的
设计又是至关重要的,那么我们该如何设计呢?一般来说,设计哈希函数时要达到两个要求:计算简单,计算复杂
的哈希函数会增加查询的时间;关键字尽可能地均分到存储地址上,这样可以减少冲突。
构造哈希函数:
#include <iostream>
#include <string>
using namespace std;
class HashTable {
private:
string *elem;
int size;
public:
HashTable() {
size = 2000;
elem = new string[size];
for (int i = 0; i < size; i++) {
elem[i] = "#";
}
}
~HashTable() {
delete[] elem;
}
int hash(string &index) {
int code = 0;
for(size_t i = 0; i < index.length(); i++) {
code = (code * 256 + index[i] + 128) % size;
}
return code;
}
};
int main() {
HashTable hashtable;
return 0;
}
那么常见的处理冲突的方法有哪些呢?
开放地址法。如果发生冲突,那么就使用某种策略寻找下一存储地址,直到找到一个不冲突的地址或者找到关键字,否则一直按这种策略继续寻找。如果冲突次数达到了上限则终止程序,表示关键字不存在哈希表里。一般常见的策略有这么几种:
1. 线性探测法,如果当前的冲突位置为 dd,那么接下来几个探测地址为 d + 1d+1,d + 2d+2,d + 3d+3 等,也就是从冲突地址往后面一个一个探测;
2. 线性补偿探测法,它形成的探测地址为 d + m,d + 2 * m,d + 3 * m 等,与线性探测法不同,这里的查找单位不是 1,而是 m,为了能遍历到哈希表里所有位置,我们设置 m和表长 size 互质;
3. 随机探测法,这种方法和前两种方法类似,这里的查找单位不是一个固定值,而是一个随机序列。
4. 二次探测法,它形成的探测地址为 d + 1^2
,d - 1^2
,d + 2^2
,d - 2^2
等,这种方法在冲突位置左右跳跃着寻找探测地址。
开放地址法计算简单快捷,处理起来方便,但是也存在不少缺点。线性探测法容易形成“堆聚”的情况,即很多记录就连在一块,而且一旦形成“堆聚”,记录会越聚越多。另外,开放地址法都有一个缺点,删除操作显得十分复杂,我们不能直接删除关键字所在的记录,否则在查找删除位置后面的元素时,可能会出现找不到的情况,因为删除位置上已经成了空地址,查找到这里时会终止查找。
链地址法。该方法将所有哈希地址相同的结点构成一个单链表,单链表的头结点存在哈希数组里。链地址法常出现在经常插入和删除的情况下。
相比 开放地址法,链地址法有以下优点:不会出现“堆聚”现象,哈希地址不同的关键字不会发生冲突;不需要重建哈希表,在开放地址法中,如果哈希表里存满关键字了就需要扩充哈希表然后重建哈希表,而在链地址法里,因为结点都是动态申请的,所以不会出现哈希表里存满关键字的情况;相比 开放地址法,链地址法关键字删除更方便,只需要找到指定结点,删除该结点即可。
开放地址法和链地址法各有千秋,适用于不同情况。当关键字规模少的时候,开放地址法比链地址法更节省空间,因为用链地址法可能会存在哈希数组出现大量空地址的情况,而在关键字规模大的情况下,链地址法就比开放地址法更节省空间,链表产生的指针域可以忽略不计,关键字多,哈希数组里产生的空地址就少了。开放地址法:
线性查找法:
#include <iostream>
#include <string>
using namespace std;
class HashTable {
private:
string *elem;
int size;
public:
HashTable() {
size = 2000;
elem = new string[size];
for (int i = 0; i < size; i++) {
elem[i] = "#";
}
}
~HashTable() {
delete[] elem;
}
int hash(string& index) {
int code = 0;
for (size_t i = 0; i < index.length(); i++) {
code = (code * 256 + index[i] + 128) % size;
}
return code;
}
bool search(string &index, int &pos, int ×) {
pos = hash(index);
times = 0;
while(elem[pos] != "#" && elem[pos] != index) {
times++;
if(times < size) {
pos = (pos + 1) % size;
} else {
return false;
}
}
if(elem[pos] == index) {
return true;
} else {
return false;
}
}
};
int main() {
HashTable hashtable;
return 0;
}
插入哈希表:
#include <iostream>
#include <string>
using namespace std;
class HashTable {
private:
string *elem;
int size;
public:
HashTable() {
size = 2000;
elem = new string[size];
for (int i = 0; i < size; i++) {
elem[i] = "#";
}
}
~HashTable() {
delete[] elem;
}
int hash(string& index) {
int code = 0;
for (size_t i = 0; i < index.length(); i++) {
code = (code * 256 + index[i] + 128) % size;
}
return code;
}
bool search(string& index, int& pos, int& times) {
pos = hash(index);
times = 0;
while (elem[pos] != "#" && elem[pos] != index) {
times++;
if (times < size) {
pos = (pos + 1) % size;
} else {
return false;
}
}
if (elem[pos] == index) {
return true;
} else {
return false;
}
}
int insert(string &index) {
int pos, times;
if(search(index, pos, times)) {
return 2;
} else if (times < size/2) {
elem[pos] = index;
return 1;
} else {
recreate();
return 0;
}
}
};
int main() {
HashTable hashtable;
string buffer;
int n;
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> buffer;
int ans = hashtable.insert(buffer);
if(ans == 0) {
cout << "insert failed!" << endl;
}
else if(ans == 1) {
cout << "insert success!" << endl;
}
else if(ans == 2) {
cout << "It already exists!" << endl;
}
}
int temp_pos, temp_times;
cin >> buffer;
if(hashtable.search(buffer, temp_pos, temp_times)) {
cout << "search success!" << endl;
} else {
cout << "search failed!" <<endl;
}
return 0;
}
树:
二叉树:
完全二叉树:
二叉树:
创建:
#include<iostream>
using namespace std;
class Node {
public: int data;
Node *lchild, *rchild;
Node(int _data) {
data = _data;
lchild = NULL;
rchild = NULL;
}
};
class BinaryTree {
private: Node *root;
public: BinaryTree() {
root = NULL;
}
};
int main() {
BinaryTree binarytree;
return 0;
}
图示二叉树的存储和遍历:
二叉树的存储:
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node *lchild, *rchild;
Node(int _data) {
data = _data;
lchild = NULL;
rchild = NULL;
}
~Node() {
if(lchild != NULL) {
delete lchild;
}
if(rchild != NULL) {
delete rchild;
}
}
};
class BinaryTree {
private:
Node *root;
public:
BinaryTree() {
root = NULL;
}
~BinaryTree() {
if(root != NULL) {
delete root;
}
}
void build_demo() {
root = new Node(1);
root->lchild = new Node(2);
root->rchild = new Node(3);
root->lchild->lchild = new Node(4);
root->lchild->rchild = new Node(5);
root->rchild->rchild = new Node(6);
}
};
int main() {
BinaryTree binarytree;
binarytree.build_demo();
return 0;
}
一棵非空的二叉树的先序遍历序列与后序遍历序列正好相反,则该二叉树一定满足?
只有一个叶子结点
二叉树先序遍历:
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node *lchild, *rchild;
Node(int _data) {
data = _data;
lchild = NULL;
rchild = NULL;
}
~Node() {
if (lchild != NULL) {
delete lchild;
}
if (rchild != NULL) {
delete rchild;
}
}
void preorder() {
cout << data << " ";
if(lchild != NULL) {
lchild->preorder();
}
if(rchild != NULL) {
rchild->preorder();
}
}
};
class BinaryTree {
private:
Node *root;
public:
BinaryTree() {
root = NULL;
}
~BinaryTree() {
if (root != NULL) {
delete root;
}
}
void build_demo() {
root = new Node(1);
root->lchild = new Node(2);
root->rchild = new Node(3);
root->lchild->lchild = new Node(4);
root->lchild->rchild = new Node(5);
root->rchild->rchild = new Node(6);
}
void preorder() {
root->preorder();
}
};
int main() {
BinaryTree binarytree;
binarytree.build_demo();
binarytree.preorder();
cout << endl;
return 0;
}
中序遍历:
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node *lchild, *rchild;
Node(int _data) {
data = _data;
lchild = NULL;
rchild = NULL;
}
~Node() {
if (lchild != NULL) {
delete lchild;
}
if (rchild != NULL) {
delete rchild;
}
}
void preorder() {
cout << data << " ";
if (lchild != NULL) {
lchild->preorder();
}
if (rchild != NULL) {
rchild->preorder();
}
}
void inorder() {
if(lchild != NULL) {
lchild->inorder();
}
cout << data << " ";
if(rchild != NULL) {
rchild->inorder();
}
}
};
class BinaryTree {
private:
Node *root;
public:
BinaryTree() {
root = NULL;
}
~BinaryTree() {
if (root != NULL) {
delete root;
}
}
void build_demo() {
root = new Node(1);
root->lchild = new Node(2);
root->rchild = new Node(3);
root->lchild->lchild = new Node(4);
root->lchild->rchild = new Node(5);
root->rchild->rchild = new Node(6);
}
void preorder() {
root->preorder();
}
void inorder() {
root->inorder();
}
};
int main() {
BinaryTree binarytree;
binarytree.build_demo();
binarytree.preorder();
cout << endl;
binarytree.inorder();
cout << endl;
return 0;
}
后序遍历:
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node *lchild, *rchild;
Node(int _data) {
data = _data;
lchild = NULL;
rchild = NULL;
}
~Node() {
if (lchild != NULL) {
delete lchild;
}
if (rchild != NULL) {
delete rchild;
}
}
void preorder() {
cout << data << " ";
if (lchild != NULL) {
lchild->preorder();
}
if (rchild != NULL) {
rchild->preorder();
}
}
void inorder() {
if (lchild != NULL) {
lchild->inorder();
}
cout << data << " ";
if (rchild != NULL) {
rchild->inorder();
}
}
void postorder() {
if(lchild != NULL) {
lchild->postorder();
}
if(rchild != NULL) {
rchild->postorder();
}
cout << data << " ";
}
};
class BinaryTree {
private:
Node *root;
public:
BinaryTree() {
root = NULL;
}
~BinaryTree() {
if (root != NULL) {
delete root;
}
}
void build_demo() {
root = new Node(1);
root->lchild = new Node(2);
root->rchild = new Node(3);
root->lchild->lchild = new Node(4);
root->lchild->rchild = new Node(5);
root->rchild->rchild = new Node(6);
}
void preorder() {
root->preorder();
}
void inorder() {
root->inorder();
}
void postorder() {
root->postorder();
}
};
int main() {
BinaryTree binarytree;
binarytree.build_demo();
binarytree.preorder();
cout << endl;
binarytree.inorder();
cout << endl;
binarytree.postorder();
cout << endl;
return 0;
}
遍历结果:
已知先序遍历和中序遍历求后序遍历:
#include<iostream>
#include<string>
using namespace std;
class Node {
public:
int data;
Node *lchild, *rchild;
Node(int _data) {
data = _data;
lchild = NULL;
rchild = NULL;
}
~Node() {
if (lchild != NULL) {
delete lchild;
}
if (rchild != NULL) {
delete rchild;
}
}
void postorder() {
if (lchild != NULL) {
lchild->postorder();
}
if (rchild != NULL) {
rchild->postorder();
}
cout << data << " ";
}
Node* build(const string &pre_str, const string &in_str, int len) {
Node* p = new Node(pre_str[0] - '0');
int pos = in_str.find(pre_str[0]);
if(pos > 0) {
p->lchild = build(pre_str.substr(1, pos), in_str.substr(0, pos), pos);
}
if(len - pos - 1 > 0) {
p->rchild = build(pre_str.substr(pos + 1), in_str.substr(pos + 1), len - pos - 1);
}
return p;
}
};
class BinaryTree {
private:
Node *root;
public:
BinaryTree() {
root = NULL;
}
~BinaryTree() {
if (root != NULL) {
delete root;
}
}
BinaryTree(const string &pre_str, const string &in_str, int len) {
root = root->build(pre_str, in_str, len);
}
void postorder() {
root->postorder();
}
};
int main() {
string pre_str = "136945827";
string in_str = "963548127";
BinaryTree binarytree(pre_str, in_str, in_str.length());
binarytree.postorder();
cout << endl;
return 0;
}
哈弗曼编码:
这一课我们来学习哈夫曼编码。哈夫曼编码是 1952 年由 David A. Huffman 提出的一种无损数据压缩的编码算法。哈夫曼编码先统计出每种字母在字符串里出现的频率,根据频率建立一棵路径带权的二叉树,也就是哈夫曼树,树上每个结点存储字母出现的频率,根结点到结点的路径即是字母的编码,频率高的字母使用较短的编码,频率低的字母使用较长的编码,使得编码后的字符串占用空间最小。那么哈夫曼是如何编码的呢?
首先统计每个字母在字符串里出现的频率,我们把每个字母看成一个结点,结点的权值即是字母出现的频率,我们把每个结点看成一棵只有根结点的二叉树,一开始把所有二叉树都放在一个集合里。接下来开始如下编码:
步骤一:从集合里取出两个根结点权值最小的树 a 和 b,构造出一棵新的二叉树 c,二叉树 c 的根结点的权值为 a 和 b 的根结点权值和,二叉树 c 的左右子树分别是 a 和 b。
步骤二:将二叉树 a 和 b 从集合里删除,把二叉树 c 加入集合里。
重复以上两个步骤,直到集合里只剩下一棵二叉树,最后剩下的就是哈夫曼树了。
我们规定每个有孩子结点的结点,到左孩子结点的路径为 0,到右孩子结点的路径为 1。每个字母的编码就是根结点到字母对应结点的路径。
讲到这里,你可能对哈夫曼编码有所了解了,为了让大家更好的理解,下面我们用一个例子来模拟下。
例如有这一个字符串“good good study day day up”,现在我们要对字符串进行哈夫曼编码,该字符串一共有 26 个字符,10 种字符,我们首先统计出每个字符的频率,然后按从大到小顺序排列如下(第二列的字符是空格):
我们把每个字符看成一个结点,权值是字符的频率,每个字符开始都是一棵只有根结点的二叉树,如下图。
1.从集合里取出根结点权值最小的两棵树 I 和 J 组成新的二叉树 IJ,根结点权值为 1 + 1 = 2,将二叉树 IJ 加入集合,把 I 和 J 从集合里删除,如下图。
2.从集合里取出根结点权值最小的两棵树 H 和 G 组成新的二叉树 HG,根结点权值为 1 + 2 = 3,将二叉树 HG 加入集合,把 H 和 G 从集合里删除,如下图。
3.从集合里取出根结点权值最小的两棵树 E 和 F 组成新的二叉树 EF,根结点权值为 2 + 2 = 4,将二叉树 EF 加入集合,把 E 和 F 从集合里删除,如下图。
4.从集合里取出根结点权值最小的两棵树 IJ 和 D 组成新的二叉树 IJD,根结点权值为 2 + 3 = 5,将二叉树 IJD 加入集合,把 IJ 和 D 从集合里删除,如下图。
5.从集合里取出根结点权值最小的两棵树 GH 和 C 组成新的二叉树 GHC,根结点权值为 3 + 4 = 7,将二叉树 GHC 加入集合,把 GH 和 C 从集合里删除,如下图。
6.从集合里取出根结点权值最小的两棵树 EF 和 B 组成新的二叉树 EFB,根结点权值为 4 + 5 = 9,将二叉树 EFB 加入集合,把 EF 和 B 从集合里删除,如下图。
7.从集合里取出根结点权值最小的两棵树 IJD 和 A 组成新的二叉树 IJDA,根结点权值为 5 + 5 = 10,将二叉树 IJDA 加入集合,把 IJD 和 A 从集合里删除,如下图。
8.从集合里取出根结点权值最小的两棵树 EFB 和 GHC 组成新的二叉树 EFBGHC,根结点权值为 9 + 7 = 16,将二叉树 EFBGHC 加入集合,把 EFB 和 GHC 从集合里删除,如下图。
9.从集合里取出根结点权值最小的两棵树 EFBGHC 和 IJDA 组成新的二叉树 EFBGHCIJDA,根结点权值为 16 + 10 = 26,将二叉树 EFBGHCIJDA 加入集合,把 EFBGHC 和 IJDA 从集合里删除,如下图。
到这里我们发现集合里就剩一棵二叉树了,那么编码结束,最后这棵二叉树就是我们要得到的哈夫曼树。接下来我们规定非叶子结点的结点,到左子树的路径记为 0,到右子树的路径记为 1,如下图:
根结点到每个叶子结点的路径便是其对应字母的编码了,于是我们可以得到:
下面我们来算一下哈夫曼树的带权路径长度 WPL,也就是每个叶子到根的距离乘以叶子权值结果之和。
WPL = 5 * 2 + 5 * 3 + 4 * 3 + 3 * 3 + 2 * 4 + 2 * 4 + 2 * 4 + 1 * 4 + 1 * 4 + 1 * 4 = 82。
我们来算下如果直接存储字符串需要多少个比特,我们知道一个字符占一个字节,一个字节占 8 个比特,所以一共需要 8 * 26 = 208 个比特。我们再来看看哈夫曼编码需要多少个比特,我们可以发现 WPL 也就是编码后原来字符串所占的比特总长度 82。显然,哈夫曼编码把原数据压缩了好多,而且没有损失。
二叉查找树:
创建:
#include<iostream>using namespace std;
class Node {
public:
int data;
Node *lchild, *rchild, *father;
Node(int _data, Node *_father = NULL) {
data = _data;
lchild = NULL;
rchild = NULL;
father = _father;
}
~Node() {
if (lchild != NULL) {
delete lchild;
}
if (rchild != NULL) {
delete rchild;
}
}
};
class BinaryTree {
private:
Node *root;
public:
BinaryTree() {
root = NULL;
}
~BinaryTree() {
if (root != NULL) {
delete root;
}
}
};
int main() {
BinaryTree binarytree;
return 0;
}
插入:
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node *lchild, *rchild, *father;
Node(int _data, Node *_father = NULL) {
data = _data;
lchild = NULL;
rchild = NULL;
father = _father;
}
~Node() {
if (lchild != NULL) {
delete lchild;
}
if (rchild != NULL) {
delete rchild;
}
}
void insert(int value) {
if(value == data) {
return;
}
else if(value > data) {
if(rchild == NULL) {
rchild = new Node(value, this);
} else {
rchild->insert(value);
}
}
else {
if(lchild == NULL) {
lchild = new Node(value, this);
} else {
lchild->insert(value);
}
}
}
};
class BinaryTree {
private:
Node *root;
public:
BinaryTree() {
root = NULL;
}
~BinaryTree() {
if (root != NULL) {
delete root;
}
}
void insert(int value) {
if(root == NULL) {
root = new Node(value);
} else {
root->insert(value);
}
}
};
int main() {
BinaryTree binarytree;
int arr[10] = { 8, 9, 10, 3, 2, 1, 6, 4, 7, 5 };
for(int i = 0; i < 10; i++) {
binarytree.insert(arr[i]);
}
return 0;
}
查找:
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node *lchild, *rchild, *father;
Node(int _data, Node *_father = NULL) {
data = _data;
lchild = NULL;
rchild = NULL;
father = _father;
}
~Node() {
if (lchild != NULL) {
delete lchild;
}
if (rchild != NULL) {
delete rchild;
}
}
void insert(int value) {
if (value == data) {
return;
} else if (value > data) {
if (rchild == NULL) {
rchild = new Node(value, this);
} else {
rchild->insert(value);
}
} else {
if (lchild == NULL) {
lchild = new Node(value, this);
} else {
lchild->insert(value);
}
}
}
Node* search(int value) {
if(data == value) {
return this;
}
else if(data < value) {
if(rchild == NULL) {
return NULL;
} else {
return rchild->search(value);
}
}
else {
if(lchild == NULL) {
return NULL;
} else {
return lchild->search(value);
}
}
}
};
class BinaryTree {
private:
Node *root;
public:
BinaryTree() {
root = NULL;
}
~BinaryTree() {
if (root != NULL) {
delete root;
}
}
void insert(int value) {
if (root == NULL) {
root = new Node(value);
} else {
root->insert(value);
}
}
bool find(int value) {
if(root->search(value) == NULL) {
return false;
} else {
return true;
}
}
};
int main() {
BinaryTree binarytree;
int arr[10] = { 8, 9, 10, 3, 2, 1, 6, 4, 7, 5 };
for (int i = 0; i < 10; i++) {
binarytree.insert(arr[i]);
}
int value;
cin >> value;
if(binarytree.find(value)) {
cout << "search success!" << endl;
} else {
cout << "search failed!" << endl;
}
return 0;
}
二叉查找树的前驱和后继:
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node *lchild, *rchild, *father;
Node(int _data, Node *_father = NULL) {
data = _data;
lchild = NULL;
rchild = NULL;
father = _father;
}
~Node() {
if (lchild != NULL) {
delete lchild;
}
if (rchild != NULL) {
delete rchild;
}
}
void insert(int value) {
if (value == data) {
return;
} else if (value > data) {
if (rchild == NULL) {
rchild = new Node(value, this);
} else {
rchild->insert(value);
}
} else {
if (lchild == NULL) {
lchild = new Node(value, this);
} else {
lchild->insert(value);
}
}
}
Node* search(int value) {
if (data == value) {
return this;
} else if (value > data) {
if (rchild == NULL) {
return NULL;
} else {
return rchild->search(value);
}
} else {
if (lchild == NULL) {
return NULL;
} else {
return lchild->search(value);
}
}
}
Node* predecessor() {
Node* temp = lchild;
while(temp != NULL && temp->rchild != NULL) {
temp = temp->rchild;
}
return temp;
}
Node* successor() {
Node* temp = rchild;
while(temp != NULL && temp->lchild != NULL) {
temp = temp->lchild;
}
return temp;
}
};
class BinaryTree {
private:
Node *root;
public:
BinaryTree() {
root = NULL;
}
~BinaryTree() {
if (root != NULL) {
delete root;
}
}
void insert(int value) {
if (root == NULL) {
root = new Node(value);
} else {
root->insert(value);
}
}
bool find(int value) {
if (root->search(value) == NULL) {
return false;
} else {
return true;
}
}
};
int main() {
BinaryTree binarytree;
int arr[10] = { 8, 9, 10, 3, 2, 1, 6, 4, 7, 5 };
for (int i = 0; i < 10; i++) {
binarytree.insert(arr[i]);
}
int value;
cin >> value;
if (binarytree.find(value)) {
cout << "search success!" << endl;
} else {
cout << "search failed!" << endl;
}
return 0;
}
删除(度为1 或 度为0的节点):
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node *lchild, *rchild, *father;
Node(int _data, Node *_father = NULL) {
data = _data;
lchild = NULL;
rchild = NULL;
father = _father;
}
~Node() {
if (lchild != NULL) {
delete lchild;
}
if (rchild != NULL) {
delete rchild;
}
}
void insert(int value) {
if (value == data) {
return;
} else if (value > data) {
if (rchild == NULL) {
rchild = new Node(value, this);
} else {
rchild->insert(value);
}
} else {
if (lchild == NULL) {
lchild = new Node(value, this);
} else {
lchild->insert(value);
}
}
}
Node* search(int value) {
if (data == value) {
return this;
} else if (value > data) {
if (rchild == NULL) {
return NULL;
} else {
return rchild->search(value);
}
} else {
if (lchild == NULL) {
return NULL;
} else {
return lchild->search(value);
}
}
}
Node* predecessor() {
Node *temp = lchild;
while (temp != NULL && temp->rchild != NULL) {
temp = temp->rchild;
}
return temp;
}
Node* successor() {
Node *temp = rchild;
while (temp != NULL && temp->lchild != NULL) {
temp = temp->lchild;
}
return temp;
}
void remove_node(Node* delete_node) {
Node* temp = NULL;
if(delete_node->lchild != NULL) {
temp = delete_node->lchild;
temp->father = delete_node->father;
delete_node->lchild = NULL;
}
if(delete_node->rchild != NULL) {
temp = delete_node->rchild;
temp->father = delete_node->father;
delete_node->rchild = NULL;
}
if(delete_node->father->lchild == delete_node) {
delete_node->father->lchild = temp;
}
else {
delete_node->father->rchild = temp;
}
delete delete_node;
}
};
class BinaryTree {
private:
Node *root;
public:
BinaryTree() {
root = NULL;
}
~BinaryTree() {
if (root != NULL) {
delete root;
}
}
void insert(int value) {
if (root == NULL) {
root = new Node(value);
} else {
root->insert(value);
}
}
bool find(int value) {
if (root->search(value) == NULL) {
return false;
} else {
return true;
}
}
};
int main() {
BinaryTree binarytree;
int arr[10] = { 8, 9, 10, 3, 2, 1, 6, 4, 7, 5 };
for (int i = 0; i < 10; i++) {
binarytree.insert(arr[i]);
}
int value;
cin >> value;
if (binarytree.find(value)) {
cout << "search success!" << endl;
} else {
cout << "search failed!" << endl;
}
return 0;
}
完整版删除:
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node *lchild, *rchild, *father;
Node(int _data, Node *_father = NULL) {
data = _data;
lchild = NULL;
rchild = NULL;
father = _father;
}
~Node() {
if (lchild != NULL) {
delete lchild;
}
if (rchild != NULL) {
delete rchild;
}
}
void insert(int value) {
if (value == data) {
return;
} else if (value > data) {
if (rchild == NULL) {
rchild = new Node(value, this);
} else {
rchild->insert(value);
}
} else {
if (lchild == NULL) {
lchild = new Node(value, this);
} else {
lchild->insert(value);
}
}
}
Node* search(int value) {
if (data == value) {
return this;
} else if (value > data) {
if (rchild == NULL) {
return NULL;
} else {
return rchild->search(value);
}
} else {
if (lchild == NULL) {
return NULL;
} else {
return lchild->search(value);
}
}
}
Node* predecessor() {
Node *temp = lchild;
while (temp != NULL && temp->rchild != NULL) {
temp = temp->rchild;
}
return temp;
}
Node* successor() {
Node *temp = rchild;
while (temp != NULL && temp->lchild != NULL) {
temp = temp->lchild;
}
return temp;
}
void remove_node(Node *delete_node) {
Node *temp = NULL;
if (delete_node->lchild != NULL) {
temp = delete_node->lchild;
temp->father = delete_node->father;
delete_node->lchild = NULL;
}
if (delete_node->rchild != NULL) {
temp = delete_node->rchild;
temp->father = delete_node->father;
delete_node->rchild = NULL;
}
if (delete_node->father->lchild == delete_node) {
delete_node->father->lchild = temp;
} else {
delete_node->father->rchild = temp;
}
delete delete_node;
}
bool delete_tree(int value) {
Node* delete_node, *current_node;
current_node = search(value);
if(current_node == NULL) {
return false;
}
if(current_node->lchild != NULL) {
delete_node = current_node->predecessor();
}
else if(current_node->rchild != NULL) {
delete_node = current_node->successor();
}
else {
delete_node = current_node;
}
current_node->data = delete_node->data;
remove_node(delete_node);
return true;
}
};
class BinaryTree {
private:
Node *root;
public:
BinaryTree() {
root = NULL;
}
~BinaryTree() {
if (root != NULL) {
delete root;
}
}
void insert(int value) {
if (root == NULL) {
root = new Node(value);
} else {
root->insert(value);
}
}
bool find(int value) {
if (root->search(value) == NULL) {
return false;
} else {
return true;
}
}
bool delete_tree(int value) {
return root->delete_tree(value);
}
};
int main() {
BinaryTree binarytree;
int arr[10] = { 8, 9, 10, 3, 2, 1, 6, 4, 7, 5 };
for (int i = 0; i < 10; i++) {
binarytree.insert(arr[i]);
}
int value;
cin >> value;
if (binarytree.find(value)) {
cout << "search success!" << endl;
} else {
cout << "search failed!" << endl;
}
cin >> value;
if(binarytree.delete_tree(value)) {
cout << "delete success!" <<endl;
} else {
cout << "delete failed!" << endl;
}
return 0;
}
平衡树:
这一章,我们来一起学习一个新的数据结构——平衡树。
还记得上一章里排序二叉树的插入么?不知道你有没有意识到,如果元素从小到大顺次插入到二叉排序树中,最终的形态会变成这样:
此时这棵二叉排序树的高度为 5。如果每次要在二叉排序树中查找元素 5,则需要每次将整棵树遍历。也就是说,二叉排序树退化成了一个有序链表,效率大大降低。
为了避免退化成一条链的情况,就有了一类改进的“更平衡”的二叉排序树。这种二叉排序树被称为 自平衡二叉查找树(balanced binary search tree,简称平衡树)。通过各种对树的形态的限制,和对应的调整方法,使得树的深度不会过大,不至于使得查询的时间复杂度退化到 O(n)。
比如刚才的排序二叉树,如果将这棵树“调整”一下,可以得到下面这棵树:
此时二叉排序树的高度减少为 3 了。在查询元素时,效率比之前要高不少。
计算机科学发展至今,已经出现了若干种平衡树,其中最先发明的是 AVL 树,这种“元老级”的平衡树会在后面的课程里介绍给你。
所有平衡树基本由以下三个特征组成:
- 自平衡条件
- 旋转操作
- 旋转的触发
平衡树通过设置合理的自平衡条件,使得二叉排序树的查找、插入等操作的性能不至于退化到 O(n),并且在进行二叉排序树的查找、插入等操作时进行判断,如果满足其中某个旋转的触发条件,则进行对应的旋转操作。
在本章里,会为你介绍 AVL 树和 SBTree 这两种平衡树,并带你实现 SBTree 的旋转和调整操作,以及用 SBTree 求解二叉排序树中第 k 大元素。最后需要你用平衡树解决一道难题。
前面的课程里已经提到过,AVL 树是最早发明的一种平衡树。AVL 树的名称来源于它的两个发明者:G.M. Adelson-Velsky 和 E.M. Landis。他们在 1962 年的论文《An algorithm for the organization of information》中将 AVL 树发表。
按照我们之前的总结,将从 自平衡条件、旋转操作 和 旋转的触发 这三个方面为你介绍 AVL 树的原理。
自平衡条件
AVL 树提出了一个概念:平衡因子(balance factor)。每个结点的平衡因子是指它左子树最大高度和右子树最大高度的差。在 AVL 树中,平衡因子为 −1、0、1 的结点都被认为是平衡的,而平衡因子为 −2、2 等其他值的结点被认为是不平衡的,需要对这个结点所在子树进行调整。
比如下面这棵树:
对结点 4 来说,平衡因子为 ∣heightleft−heightright∣=∣3−1∣=2>1,所以不平衡。
而下面这棵树:
树上的所有结点都满足 AVL 的平衡条件,你如果有兴趣可以自己算一下。
旋转操作
单旋
在 AVL 树中,一共有两种单旋操作:左旋和右旋。AVL 树通过一系列左旋和右旋操作,将不平衡的树调整为平衡树。
左旋操作的演示如下:
通过进行左旋操作,使得原先的根 2 变成了其右孩子 4 的左孩子,而 4 原先的左孩子 3 变成了 2 的右孩子。“左旋”这个名字是不是很形象呀。
对应的,右旋操作的演示如下:
通过右旋操作,使得原先的根 5 变成了其左孩子 3 的右孩子,而 3 原先的右孩子变成了 5 的左孩子。
多旋
AVL 树中还有两种复合旋转操作(即“多旋”),由两次单旋操作组合而成。
第一种是左旋加右旋:
第二种是右旋加左旋:
旋转的触发
插入操作
在插入一个元素后不断回溯的过程中,如果因此导致结点不平衡,则根据不平衡情况(一定是一边子树比另一边子树的高度大 2)进行对应的旋转:
-
左子树比右子树的高度大 2:
- 如果新增元素插入到左儿子的左子树中,则进行右旋操作。(LL 型调整)
- 如果新增元素插入到左儿子的右子树中,则进行左旋加右旋操作。(LR 型调整)
-
右子树比左子树的高度大 2:
- 如果新增元素插入到右儿子的右子树中,则进行左旋操作。(RR 型调整)
- 如果新增元素插入到右儿子的左子树中,则进行右旋加左旋操作。(RL 型调整)
删除操作
类似的,在删除一个元素后不断回溯的过程中,如果因此导致结点不平衡,则和插入操作采用相同的调整操作,确保在删除以后整棵树依然是平衡的。
你可以思考下,AVL 在删除过程中如果不进行调整操作,是否能接受呢?
好了,现在我们已经了解 AVL 树的平衡策略及一系列维护平衡的操作。在后面的课程中,我们会继续巩固 AVL 树的相关内容,之后会为你介绍另外一种平衡树——SBTree。
除了 AVL 树,还有众多的平衡树,比较著名的有红黑树、Treap、替罪羊树等。我们接下来的课程,则要着重介绍另外一种非常年轻的平衡树——Size Balanced Tree。
和 AVL 树类似,这节课我们依然从 自平衡条件、旋转操作 和 旋转的触发 这三个方面介绍 SBTree。
自平衡条件
还记得 AVL 树的自平衡条件么?在 AVL 树中,任何结点的两个子树的高度最大差别为 1。而对于 SBT,它的自平衡条件会显得稍微复杂一些:对于每个结点 t,同时满足
size[right[t]]≥max(size[left[left[t]]],size[right[left[t]]])
size[left[t]]≥max(size[left[right[t]]],size[right[right[t]]])
其中 size[t] 表示 t 结点所在子树的结点个数。
公式看起来很复杂,形象一点来说,就是每个结点所在子树的结点个数不小于其兄弟的两个孩子所在子树的结点个数。
比如对于这棵排序二叉树
对于结点 39 来说,就不满足 SBTree 的自平衡条件。
旋转操作
旋转操作和 AVL 树的左旋右旋是完全一样的。我们再来复习一遍这两种非常通用的旋转操作。
对于下面这棵树:
我们对 10 进行右旋操作后的结果为:
那么左旋呢?我们换一棵树:
我们对 39 进行左旋操作后的结果为:
旋转的触发
前面已经为你介绍了平衡树的旋转操作,那么在调整过程中,是如何触发左旋、右旋操作的呢?
在调整过程中,一共有 4 种会触发旋转的情况:
-
LL 型:size[left[left[t]]]>size[right[t]]:首先对子树 t 执行右旋操作,旋转以后对 t 的右子树进行调整,之后再对子树 t 进行调整。
-
LR 型:size[right[left[t]]]>size[right[t]]:首先对 t 的左子树执行左旋操作,再对 t 进行右旋操作。之后分别调整结点 t 的左右子树,最终对结点 t进行调整。
-
RR 型:size[right[right[t]]]>size[left[t]]:首先对 t 执行左旋操作,旋转以后对 t 的左子树进行调整,之后再对 t 进行调整。
-
RL 型:size[left[right[t]]]>size[left[t]]:首先对结点 t 的右子树执行右旋操作,再对 t 进行左旋操作。之后分别调整 t 的左右子树,最终对 t 进行调整。
通过递归的进行调整,我们可以最终让不平衡的 SBTree 恢复平衡状态。可以证明,调整操作的均摊时间复杂度为 O(1)。
和 AVL 不太一样的是,SBTree 只有在插入时才可能触发调整,而 不需要在删除结点以后进行调整。
从理论上说,SBTree 和 AVL 树相比在均摊时间复杂度上没有区别,每次查询、插入和删除的时间复杂度都为 O(logn)。在实际运用中,SBTree 在查询操作较多的情况下会有效率上的优势。加之为了维护平衡性记录了每个结点所在子树大小(即子树内结点个数),相比其他平衡树而言,更便于求解第 k 大元素、或求解元素的秩(rank)等类似问题。
后面的课程中,我们会实现 SBTree 的左旋、右旋、调整操作,并使用 SBTree 求解第 k 大元素。在最后使用平衡树解决一道难题。
左旋:
#include <iostream>
using namespace std;
class SBTNode {
public:
int data, size, value;
SBTNode * lchild, * rchild, * father;
SBTNode(int init_data, int init_size = 0, SBTNode * init_father = NULL);
~SBTNode();
void insert(int value);
SBTNode * search(int value);
SBTNode * predecessor();
SBTNode * successor();
void remove_node(SBTNode * delete_node);
bool remove(int value);
};
class BinaryTree {
private:
SBTNode * root;
public:
BinaryTree();
~BinaryTree();
void insert(int value);
bool find(int value);
bool remove(int value);
};
SBTNode ZERO(0);
SBTNode * ZPTR = &ZERO;
SBTNode::SBTNode(int init_data, int init_size, SBTNode * init_father) {
data = init_data;
size = init_size;
lchild = ZPTR;
rchild = ZPTR;
father = init_father;
}
SBTNode::~SBTNode() {
if (lchild != ZPTR) {
delete lchild;
}
if (rchild != ZPTR) {
delete rchild;
}
}
SBTNode * left_rotate(SBTNode * node) {
SBTNode *temp = node->rchild;
node->rchild = temp->lchild;
temp->lchild->father = node;
temp->lchild = node;
temp->father = node->father;
node->father = temp;
temp->size = node->size;
node->size = node->lchild->size + node->rchild->size + 1;
return temp;
}
SBTNode * right_rotate(SBTNode * node) {
}
SBTNode * maintain(SBTNode * node, bool flag) {
}
SBTNode * insert(SBTNode * node, int value) {
if (value == node->data) {
return node;
} else {
node->size++;
if (value > node->data) {
if (node->rchild == ZPTR) {
node->rchild = new SBTNode(value, 1, node);
} else {
node->rchild = insert(node->rchild, value);
}
} else {
if (node->lchild == ZPTR) {
node->lchild = new SBTNode(value, 1, node);
} else {
node->lchild = insert(node->lchild, value);
}
}
}
return maintain(node, value > node->data);
}
SBTNode * SBTNode::search(int value) {
if (data == value) {
return this;
} else if (value > data) {
if (rchild == ZPTR) {
return ZPTR;
} else {
return rchild->search(value);
}
} else {
if (lchild == ZPTR) {
return ZPTR;
} else {
return lchild->search(value);
}
}
}
SBTNode * SBTNode::predecessor() {
SBTNode * temp = lchild;
while (temp != ZPTR && temp->rchild != ZPTR) {
temp = temp->rchild;
}
return temp;
}
SBTNode * SBTNode::successor() {
SBTNode * temp = rchild;
while (temp != ZPTR && temp->lchild != ZPTR) {
temp = temp->lchild;
}
return temp;
}
void SBTNode::remove_node(SBTNode * delete_node) {
SBTNode * temp = ZPTR;
if (delete_node->lchild != ZPTR) {
temp = delete_node->lchild;
temp->father = delete_node->father;
delete_node->lchild = ZPTR;
}
if (delete_node->rchild != ZPTR) {
temp = delete_node->rchild;
temp->father = delete_node->father;
delete_node->rchild = ZPTR;
}
if (delete_node->father->lchild == delete_node) {
delete_node->father->lchild = temp;
} else {
delete_node->father->rchild = temp;
}
temp = delete_node;
while (temp != NULL) {
temp->size--;
temp = temp->father;
}
delete delete_node;
}
bool SBTNode::remove(int value) {
SBTNode * delete_node, * current_node;
current_node = search(value);
if (current_node == ZPTR) {
return false;
}
size--;
if (current_node->lchild != ZPTR) {
delete_node = current_node->predecessor();
} else if (current_node->rchild != ZPTR) {
delete_node = current_node->successor();
} else {
delete_node = current_node;
}
current_node->data = delete_node->data;
remove_node(delete_node);
return true;
}
BinaryTree::BinaryTree() {
root = NULL;
}
BinaryTree::~BinaryTree() {
if (root != NULL) {
delete root;
}
}
void BinaryTree::insert(int value) {
if (root == NULL) {
root = new SBTNode(value, 1);
} else {
root = ::insert(root, value);
}
}
bool BinaryTree::find(int value) {
if (root->search(value) == NULL) {
return false;
} else {
return true;
}
}
bool BinaryTree::remove(int value) {
return root->remove(value);
}
int main() {
return 0;
}
右旋:
#include <iostream>
using namespace std;
class SBTNode {
public:
int data, size, value;
SBTNode * lchild, * rchild, * father;
SBTNode(int init_data, int init_size = 0, SBTNode * init_father = NULL);
~SBTNode();
void insert(int value);
SBTNode * search(int value);
SBTNode * predecessor();
SBTNode * successor();
void remove_node(SBTNode * delete_node);
bool remove(int value);
};
class BinaryTree {
private:
SBTNode * root;
public:
BinaryTree();
~BinaryTree();
void insert(int value);
bool find(int value);
bool remove(int value);
};
SBTNode ZERO(0);
SBTNode * ZPTR = &ZERO;
SBTNode::SBTNode(int init_data, int init_size, SBTNode * init_father) {
data = init_data;
size = init_size;
lchild = ZPTR;
rchild = ZPTR;
father = init_father;
}
SBTNode::~SBTNode() {
if (lchild != ZPTR) {
delete lchild;
}
if (rchild != ZPTR) {
delete rchild;
}
}
SBTNode * left_rotate(SBTNode * node) {
SBTNode * temp = node->rchild;
node->rchild = temp->lchild;
temp->lchild->father = node;
temp->lchild = node;
temp->father = node->father;
node->father = temp;
temp->size = node->size;
node->size = node->lchild->size + node->rchild->size + 1;
return temp;
}
SBTNode * right_rotate(SBTNode * node) {
SBTNode *temp = node->lchild;
node->lchild = temp->rchild;
temp->rchild->father = node;
temp->rchild = node;
temp->father = node->father;
node->father = temp;
temp->size = node->size;
node->size = node->lchild->size + node->rchild->size + 1;
return temp;
}
SBTNode * maintain(SBTNode * node, bool flag) {
}
SBTNode * insert(SBTNode * node, int value) {
if (value == node->data) {
return node;
} else {
node->size++;
if (value > node->data) {
if (node->rchild == ZPTR) {
node->rchild = new SBTNode(value, 1, node);
} else {
node->rchild = insert(node->rchild, value);
}
} else {
if (node->lchild == ZPTR) {
node->lchild = new SBTNode(value, 1, node);
} else {
node->lchild = insert(node->lchild, value);
}
}
}
return maintain(node, value > node->data);
}
SBTNode * SBTNode::search(int value) {
if (data == value) {
return this;
} else if (value > data) {
if (rchild == ZPTR) {
return ZPTR;
} else {
return rchild->search(value);
}
} else {
if (lchild == ZPTR) {
return ZPTR;
} else {
return lchild->search(value);
}
}
}
SBTNode * SBTNode::predecessor() {
SBTNode * temp = lchild;
while (temp != ZPTR && temp->rchild != ZPTR) {
temp = temp->rchild;
}
return temp;
}
SBTNode * SBTNode::successor() {
SBTNode * temp = rchild;
while (temp != ZPTR && temp->lchild != ZPTR) {
temp = temp->lchild;
}
return temp;
}
void SBTNode::remove_node(SBTNode * delete_node) {
SBTNode * temp = ZPTR;
if (delete_node->lchild != ZPTR) {
temp = delete_node->lchild;
temp->father = delete_node->father;
delete_node->lchild = ZPTR;
}
if (delete_node->rchild != ZPTR) {
temp = delete_node->rchild;
temp->father = delete_node->father;
delete_node->rchild = ZPTR;
}
if (delete_node->father->lchild == delete_node) {
delete_node->father->lchild = temp;
} else {
delete_node->father->rchild = temp;
}
temp = delete_node;
while (temp != NULL) {
temp->size--;
temp = temp->father;
}
delete delete_node;
}
bool SBTNode::remove(int value) {
SBTNode * delete_node, * current_node;
current_node = search(value);
if (current_node == ZPTR) {
return false;
}
size--;
if (current_node->lchild != ZPTR) {
delete_node = current_node->predecessor();
} else if (current_node->rchild != ZPTR) {
delete_node = current_node->successor();
} else {
delete_node = current_node;
}
current_node->data = delete_node->data;
remove_node(delete_node);
return true;
}
BinaryTree::BinaryTree() {
root = NULL;
}
BinaryTree::~BinaryTree() {
if (root != NULL) {
delete root;
}
}
void BinaryTree::insert(int value) {
if (root == NULL) {
root = new SBTNode(value, 1);
} else {
root = ::insert(root, value);
}
}
bool BinaryTree::find(int value) {
if (root->search(value) == NULL) {
return false;
} else {
return true;
}
}
bool BinaryTree::remove(int value) {
return root->remove(value);
}
int main() {
return 0;
}
在完成了左旋和右旋操作之后,我们要实现调用两种单旋操作的逻辑,也就是调整操作。
在插入之后,一共有 4 种触发旋转的情况,分别为 LL 型、LR 型、RR 型和 RL 型。
通过旋转的名称,可以很直观的想到其对应的不平衡的情况——比如 LR 型,就意味着左子树(L)的右子树(R)的元素个数过大。
还记得我们之前讲到过的,SBTree 的平衡条件么?一共有两个:
a:size[right[t]]≥max(size[left[left[t]]],size[right[left[t]]])
b:size[left[t]]≥max(size[left[right[t]]],size[right[right[t]]])
对于 LL 和 LR 型,违反了平衡条件 a;对于 RR 和 RL 型,则违反了平衡条件 b。
对应的,LL 型和 LR 型都一定能保证平衡条件 b 的成立;RR 型和 RL 型也都能一定保证平衡条件 a 的成立。
这意味着什么呢?
意味着,我们只需要检查其中一半的情况即可,来避免无谓的判断。我们可以将算法伪代码简化如下:
- 如果在处理左子树更高的情况:
- LL 型:右旋 t。
- LR 型:左旋 t 的左子树,再右旋 t。
- 如果在处理右子树更高的情况:
- RR 型:左旋 t。
- RL 型:右旋 t 的右子树,再左旋 t。
- 递归调整左子树其中左子树的左子树更高的情况
- 递归调整右子树其中右子树的右子树更高的情况
- 递归调整当前子树其中左子树更高的情况
- 递归调整当前子树其中右子树更高的情况
为什么可以不考虑右子树其中右子树的左子树更高的情况呢?因为这种情况在第 6 步已经被处理了。左子树其中左子树的右子树更高的情况也类似。因此我们可以通过和之前介绍的相比更简洁、高效的调整算法实现对 SBTree 的维护。
在下节课中,我们会最终实现这个操作。
SBTree调整的实现:
#include <iostream>
using namespace std;
class SBTNode {
public:
int data, size, value;
SBTNode * lchild, * rchild, * father;
SBTNode(int init_data, int init_size = 0, SBTNode * init_father = NULL);
~SBTNode();
void insert(int value);
SBTNode * search(int value);
SBTNode * predecessor();
SBTNode * successor();
void remove_node(SBTNode * delete_node);
bool remove(int value);
};
class BinaryTree {
private:
SBTNode * root;
public:
BinaryTree();
~BinaryTree();
void insert(int value);
bool find(int value);
bool remove(int value);
};
SBTNode ZERO(0);
SBTNode * ZPTR = &ZERO;
SBTNode::SBTNode(int init_data, int init_size, SBTNode * init_father) {
data = init_data;
size = init_size;
lchild = ZPTR;
rchild = ZPTR;
father = init_father;
}
SBTNode::~SBTNode() {
if (lchild != ZPTR) {
delete lchild;
}
if (rchild != ZPTR) {
delete rchild;
}
}
SBTNode * left_rotate(SBTNode * node) {
SBTNode * temp = node->rchild;
node->rchild = temp->lchild;
temp->lchild->father = node;
temp->lchild = node;
temp->father = node->father;
node->father = temp;
temp->size = node->size;
node->size = node->lchild->size + node->rchild->size + 1;
return temp;
}
SBTNode * right_rotate(SBTNode * node) {
SBTNode* temp = node->lchild;
node->lchild = temp->rchild;
temp->rchild->father = node;
temp->rchild = node;
temp->father = node->father;
node->father = temp;
temp->size = node->size;
node->size = node->lchild->size + node->rchild->size + 1;
return temp;
}
SBTNode * maintain(SBTNode * node, bool flag) {
if(flag == false) {
if(node->lchild->lchild->size > node->rchild->size) {
node = right_rotate(node);
} else if(node->lchild->rchild->size > node->rchild->size) {
node->lchild = left_rotate(node->lchild);
node = right_rotate(node);
} else {
return node;
}
}
else {
if(node->rchild->rchild->size > node->lchild->size) {
node = left_rotate(node);
} else if(node->rchild->lchild->size > node->lchild->size) {
node->rchild = right_rotate(node->rchild);
node = left_rotate(node);
} else {
return node;
}
}
node->lchild = maintain(node->lchild, false);
node->rchild = maintain(node->rchild, true);
node = maintain(node, false);
node = maintain(node, true);
return node;
}
SBTNode * insert(SBTNode * node, int value) {
if (value == node->data) {
return node;
} else {
node->size++;
if (value > node->data) {
if (node->rchild == ZPTR) {
node->rchild = new SBTNode(value, 1, node);
} else {
node->rchild = insert(node->rchild, value);
}
} else {
if (node->lchild == ZPTR) {
node->lchild = new SBTNode(value, 1, node);
} else {
node->lchild = insert(node->lchild, value);
}
}
}
return maintain(node, value > node->data);
}
SBTNode * SBTNode::search(int value) {
if (data == value) {
return this;
} else if (value > data) {
if (rchild == ZPTR) {
return ZPTR;
} else {
return rchild->search(value);
}
} else {
if (lchild == ZPTR) {
return ZPTR;
} else {
return lchild->search(value);
}
}
}
SBTNode * SBTNode::predecessor() {
SBTNode * temp = lchild;
while (temp != ZPTR && temp->rchild != ZPTR) {
temp = temp->rchild;
}
return temp;
}
SBTNode * SBTNode::successor() {
SBTNode * temp = rchild;
while (temp != ZPTR && temp->lchild != ZPTR) {
temp = temp->lchild;
}
return temp;
}
void SBTNode::remove_node(SBTNode * delete_node) {
SBTNode * temp = ZPTR;
if (delete_node->lchild != ZPTR) {
temp = delete_node->lchild;
temp->father = delete_node->father;
delete_node->lchild = ZPTR;
}
if (delete_node->rchild != ZPTR) {
temp = delete_node->rchild;
temp->father = delete_node->father;
delete_node->rchild = ZPTR;
}
if (delete_node->father->lchild == delete_node) {
delete_node->father->lchild = temp;
} else {
delete_node->father->rchild = temp;
}
temp = delete_node;
while (temp != NULL) {
temp->size--;
temp = temp->father;
}
delete delete_node;
}
bool SBTNode::remove(int value) {
SBTNode * delete_node, * current_node;
current_node = search(value);
if (current_node == ZPTR) {
return false;
}
size--;
if (current_node->lchild != ZPTR) {
delete_node = current_node->predecessor();
} else if (current_node->rchild != ZPTR) {
delete_node = current_node->successor();
} else {
delete_node = current_node;
}
current_node->data = delete_node->data;
remove_node(delete_node);
return true;
}
BinaryTree::BinaryTree() {
root = NULL;
}
BinaryTree::~BinaryTree() {
if (root != NULL) {
delete root;
}
}
void BinaryTree::insert(int value) {
if (root == NULL) {
root = new SBTNode(value, 1);
} else {
root = ::insert(root, value);
}
}
bool BinaryTree::find(int value) {
if (root->search(value) == NULL) {
return false;
} else {
return true;
}
}
bool BinaryTree::remove(int value) {
return root->remove(value);
}
int main() {
BinaryTree binarytree;
int arr[10] = { 8, 9, 10, 3, 2, 1, 6, 4, 7, 5 };
for (int i = 0; i < 10; i++) {
binarytree.insert(arr[i]);
}
int value;
cin >> value;
if (binarytree.find(value)) {
cout << "search success!" << endl;
} else {
cout << "search failed!" << endl;
}
cin >> value;
if (binarytree.remove(value)) {
cout << "delete success!" << endl;
} else {
cout << "delete failed!" << endl;
}
return 0;
}
SBTree求第k小元素:
#include <iostream>
using namespace std;
class SBTNode {
public:
int data, size, value;
SBTNode * lchild, * rchild, * father;
SBTNode(int init_data, int init_size = 0, SBTNode * init_father = NULL);
~SBTNode();
void insert(int value);
SBTNode * search(int value);
SBTNode * predecessor();
SBTNode * successor();
void remove_node(SBTNode * delete_node);
bool remove(int value);
int select(int k);
};
class BinaryTree {
private:
SBTNode * root;
public:
BinaryTree();
~BinaryTree();
void insert(int value);
bool find(int value);
bool remove(int value);
int select(int k);
};
SBTNode ZERO(0);
SBTNode * ZPTR = &ZERO;
SBTNode::SBTNode(int init_data, int init_size, SBTNode * init_father) {
data = init_data;
size = init_size;
lchild = ZPTR;
rchild = ZPTR;
father = init_father;
}
SBTNode::~SBTNode() {
if (lchild != ZPTR) {
delete lchild;
}
if (rchild != ZPTR) {
delete rchild;
}
}
SBTNode * left_rotate(SBTNode * node) {
SBTNode * temp = node->rchild;
node->rchild = temp->lchild;
temp->lchild->father = node;
temp->lchild = node;
temp->father = node->father;
node->father = temp;
temp->size = node->size;
node->size = node->lchild->size + node->rchild->size + 1;
return temp;
}
SBTNode * right_rotate(SBTNode * node) {
SBTNode * temp = node->lchild;
node->lchild = temp->rchild;
temp->rchild->father = node;
temp->rchild = node;
temp->father = node->father;
node->father = temp;
temp->size = node->size;
node->size = node->lchild->size + node->rchild->size + 1;
return temp;
}
SBTNode * maintain(SBTNode * node, bool flag) {
if (flag == false) {
if (node->lchild->lchild->size > node->rchild->size) {
node = right_rotate(node);
} else if (node->lchild->rchild->size > node->rchild->size) {
node->lchild = left_rotate(node->lchild);
node = right_rotate(node);
} else {
return node;
}
} else {
if (node->rchild->rchild->size > node->lchild->size) {
node = left_rotate(node);
} else if (node->rchild->lchild->size > node->lchild->size) {
node->rchild = right_rotate(node->rchild);
node = left_rotate(node);
} else {
return node;
}
}
node->lchild = maintain(node->lchild, false);
node->rchild = maintain(node->rchild, true);
node = maintain(node, false);
node = maintain(node, true);
return node;
}
SBTNode * insert(SBTNode * node, int value) {
if (value == node->data) {
return node;
} else {
node->size++;
if (value > node->data) {
if (node->rchild == ZPTR) {
node->rchild = new SBTNode(value, 1, node);
} else {
node->rchild = insert(node->rchild, value);
}
} else {
if (node->lchild == ZPTR) {
node->lchild = new SBTNode(value, 1, node);
} else {
node->lchild = insert(node->lchild, value);
}
}
}
return maintain(node, value > node->data);
}
SBTNode * SBTNode::search(int value) {
if (data == value) {
return this;
} else if (value > data) {
if (rchild == ZPTR) {
return ZPTR;
} else {
return rchild->search(value);
}
} else {
if (lchild == ZPTR) {
return ZPTR;
} else {
return lchild->search(value);
}
}
}
SBTNode * SBTNode::predecessor() {
SBTNode * temp = lchild;
while (temp != ZPTR && temp->rchild != ZPTR) {
temp = temp->rchild;
}
return temp;
}
SBTNode * SBTNode::successor() {
SBTNode * temp = rchild;
while (temp != ZPTR && temp->lchild != ZPTR) {
temp = temp->lchild;
}
return temp;
}
void SBTNode::remove_node(SBTNode * delete_node) {
SBTNode * temp = ZPTR;
if (delete_node->lchild != ZPTR) {
temp = delete_node->lchild;
temp->father = delete_node->father;
delete_node->lchild = ZPTR;
}
if (delete_node->rchild != ZPTR) {
temp = delete_node->rchild;
temp->father = delete_node->father;
delete_node->rchild = ZPTR;
}
if (delete_node->father->lchild == delete_node) {
delete_node->father->lchild = temp;
} else {
delete_node->father->rchild = temp;
}
temp = delete_node;
while (temp != NULL) {
temp->size--;
temp = temp->father;
}
delete delete_node;
}
bool SBTNode::remove(int value) {
SBTNode * delete_node, * current_node;
current_node = search(value);
if (current_node == ZPTR) {
return false;
}
size--;
if (current_node->lchild != ZPTR) {
delete_node = current_node->predecessor();
} else if (current_node->rchild != ZPTR) {
delete_node = current_node->successor();
} else {
delete_node = current_node;
}
current_node->data = delete_node->data;
remove_node(delete_node);
return true;
}
int SBTNode::select(int k) {
int rank = lchild->size + 1;
if(rank == k) {
return data;
}
else if(k < rank) {
return lchild->select(k);
}
else {
return rchild->select(k - rank);
}
}
BinaryTree::BinaryTree() {
root = NULL;
}
BinaryTree::~BinaryTree() {
if (root != NULL) {
delete root;
}
}
void BinaryTree::insert(int value) {
if (root == NULL) {
root = new SBTNode(value, 1);
} else {
root = ::insert(root, value);
}
}
bool BinaryTree::find(int value) {
if (root->search(value) == NULL) {
return false;
} else {
return true;
}
}
bool BinaryTree::remove(int value) {
return root->remove(value);
}
int BinaryTree::select(int k) {
return root->select(k);
}
int main() {
BinaryTree binarytree;
int arr[10] = { 8, 9, 10, 3, 2, 1, 6, 4, 7, 5 };
for (int i = 0; i < 10; i++) {
binarytree.insert(arr[i]);
}
int k;
cin >> k;
cout << binarytree.select(k) << endl;
return 0;
}
堆:
堆可以看成是一棵完全二叉树,除最后一层以外,它的每一层都是填满的,最后一层从左到右依次填入。堆还有一个特殊的性质,下面我们用 NBA 东西部对阵图来进行说明。
在对阵图中,离总决赛越接近的球队,实力也就越强。如果我们认为火箭能够赢快船,快船能够赢马刺,那么火箭就一定能够赢马刺。则对于西部进入总决赛的勇士来说,他便可以赢下西部的所有季后赛球队,也就是说勇士队是西部最厉害的球队。同样的,对于赢得四分之一决赛的火箭队来说,他是图中西部下半区四支球队里最厉害的。
现在我们抽象到一颗完全二叉树上,球队抽象为树上的结点,而球队实力则抽象为结点的权重。对于堆上的任意一个结点来说,越接近顶部,结点的权值也就越大,并且它的权值大于等于它所在子树的所有结点的权值。我们对具有这样性质的完全二叉树叫做堆。
特别指出的是,如果一颗完全二叉树上每个结点的权值小于等于它所在子树的任意结点的权值,也被称为堆。为了区分这两种堆,我们把根结点权值大于等于树中结点权值的堆称为大根堆,根结点权值小于等于树中结点权值的堆则称为小根堆。
我们知道,从逻辑上看,堆可以看成是一棵完全二叉树,具有 N 个元素的堆,高度为 O(logN)。但实际上,我们并不需要真的维护一棵完全二叉树,而只需用一个数组来存储,完全二叉树和数组的对应关系如下图所示。堆按从上到下,从左到右的顺序,依次存储在下标从 1 开始的数组里,图(a)是一个大根堆,图(b)是这个堆对应的数组。
由于堆是一棵完全二叉树,堆的插入和删除操作的时间复杂度为 O(logN),logN 指的就是这颗完全二叉树的高度。
堆有一个重要的性质,称为堆序性,即堆中每个结点的权值都大于等于(或小于等于)其子树任意结点的权值。
为了维护堆的堆序性,在修改堆中结点时会对堆进行调整,调整的时间复杂度为 O(logN),在后面的课程里我们会详细介绍如何实现堆的调整。堆的调整有两种,分别是自下而上的调整和自上而下的调整,即上滤和下滤。
堆通常应用在堆排序里,堆排序是一种高效的排序算法,时间复杂度为 O(N∗logN)。另外,堆也可以用于实现优先队列。优先队列我们会在后面的课程里详细介绍。
堆的功能如此强大,那我们该如何实现堆呢?又该如何用堆来实现堆排序呢?堆还有什么其他应用呢?接下来随蒜头君一起,来学习堆的实现和使用吧。
正确答案: B 你的答案: D (错误)
78,45,57,25,41,89
89,78,57,25,41,45
89,78,25,45,41,57
89,45,78,41,57,25
创建:
#include<iostream>
using namespace std;
class Heap {
private: int *data, size;
public: Heap(int length_input) {
data = new int[length_input];
size = 0;
}
~Heap() {
delete[] data;
}
};
int main() {
Heap heap(100);
return 0;
}
插入:
#include<iostream>
using namespace std;
class Heap {
private:
int *data, size;
public:
Heap(int length_input) {
data = new int[length_input];
size = 0;
}
~Heap() {
delete[] data;
}
void push(int value) {
data[size] = value;
int current = size;
int father = (current - 1) / 2;
while(data[current] > data[father]) {
swap(data[current], data[father]);
current = father;
father = (current - 1) / 2;
}
size++;
}
};
int main() {
int arr[10] = { 12, 9, 30, 24, 30, 4, 55, 64, 22, 37 };
Heap heap(100);
for(int i = 0; i < 10; i++) {
heap.push(arr[i]);
}
return 0;
}
输出:
#include<iostream>
using namespace std;
class Heap {
private:
int *data, size;
public:
Heap(int length_input) {
data = new int[length_input];
size = 0;
}
~Heap() {
delete[] data;
}
void push(int value) {
data[size] = value;
int current = size;
int father = (current - 1) / 2;
while (data[current] > data[father]) {
swap(data[current], data[father]);
current = father;
father = (current - 1) / 2;
}
size++;
}
void output() {
for(int i = 0; i < size; i++) {
cout << data[i] << " ";
}
cout << endl;
}
};
int main() {
int arr[10] = { 12, 9, 30, 24, 30, 4, 55, 64, 22, 37 };
Heap heap(100);
for (int i = 0; i < 10; i++) {
heap.push(arr[i]);
}
heap.output();
return 0;
}
获取和删除堆顶元素:
#include<iostream>
using namespace std;
class Heap {
private:
int *data, size;
public:
Heap(int length_input) {
data = new int[length_input];
size = 0;
}
~Heap() {
delete[] data;
}
void push(int value) {
data[size] = value;
int current = size;
int father = (current - 1) / 2;
while (data[current] > data[father]) {
swap(data[current], data[father]);
current = father;
father = (current - 1) / 2;
}
size++;
}
void output() {
for (int i = 0; i < size; i++) {
cout << data[i] << " ";
}
cout << endl;
}
int top() {
return data[0];
}
void update(int pos, int n) { //下滤
int lchild = 2 * pos + 1, rchild = 2 * pos + 2;
int max_value = pos;
if(lchild < n && data[lchild] > data[max_value]) {
max_value = lchild;
}
if(rchild < n && data[rchild] > data[max_value]) {
max_value = rchild;
}
if(max_value != pos) {
swap(data[pos], data[max_value]);
update(max_value, n);
}
}
void pop() {
swap(data[0], data[size - 1]);
size--;
update(0, size);
}
};
int main() {
int arr[10] = { 12, 9, 30, 24, 30, 4, 55, 64, 22, 37 };
Heap heap(100);
for (int i = 0; i < 10; i++) {
heap.push(arr[i]);
}
heap.output();
cout << heap.top() << endl;
heap.pop();
heap.output();
return 0;
}
堆排序:
#include<iostream>
using namespace std;
class Heap {
private:
int *data, size;
public:
Heap(int length_input) {
data = new int[length_input];
size = 0;
}
~Heap() {
delete[] data;
}
void push(int value) {
data[size] = value;
int current = size;
int father = (current - 1) / 2;
while (data[current] > data[father]) {
swap(data[current], data[father]);
current = father;
father = (current - 1) / 2;
}
size++;
}
void output() {
for (int i = 0; i < size; i++) {
cout << data[i] << " ";
}
cout << endl;
}
int top() {
return data[0];
}
void update(int pos, int n) {
int lchild = 2 * pos + 1, rchild = 2 * pos + 2;
int max_value = pos;
if (lchild < n && data[lchild] > data[max_value]) {
max_value = lchild;
}
if (rchild < n && data[rchild] > data[max_value]) {
max_value = rchild;
}
if (max_value != pos) {
swap(data[pos], data[max_value]);
update(max_value, n);
}
}
void pop() {
swap(data[0], data[size - 1]);
size--;
update(0, size);
}
void heap_sort() {
for(int i = size - 1; i >= 1; i--) {
swap(data[i], data[0]);
update(0, i);
}
}
};
int main() {
int arr[10] = { 12, 9, 30, 24, 30, 4, 55, 64, 22, 37 };
Heap heap(100);
for (int i = 0; i < 10; i++) {
heap.push(arr[i]);
}
heap.output();
cout << heap.top() << endl;
heap.pop();
heap.output();
heap.heap_sort();
heap.output();
return 0;
}
我们在之前的课程中已经接触了队列这个数据结构。利用队列先进先出的性质,可以用来解决很多实际问题,但对于一些特殊的问题,队列是无法解决的。
例如在医院里,重症急诊患者肯定不能像普通患者那样依次排队就诊。这时候,我们就需要一种新的数据结构——优先队列,先访问优先级高的元素(例如这里的重症急诊患者)。
通过第二章的学习,我们知道队列是一种先进先出的数据结构,元素从队尾进入,从队首删除。相比队列,优先队列里的元素增加了优先级的属性,优先级高的元素先被删除。
优先队列有什么用途呢?
它和我们刚学的堆有什么关系呢?
优先队列内部一般是用堆来实现的。我们知道堆的插入、删除操作的时间复杂度都是 O(logN),自然优先队列的插入、删除操作的时间复杂度也都是 O(logN)。堆中的堆顶元素就是优先队列的队首元素。
对于大根堆实现的优先队列,总是优先级高的元素先被删除;相对的,对于小根堆实现的优先队列,总是优先级低的元素先被删除。对于后者,我们也称之为优先队列。
优先队列可以用于解决哈夫曼编码问题。这个问题我们在二叉树一章最后给大家介绍过噢,后面的课程中我们会为大家讲解如何用优先队列来解决它。此外,优先队列还能解决任务调度问题,例如操作系统的进程调度问题。
在 C++ 的 STL 里,有封装好的优先队列 priority_queue,它包含在头文件 <queue> 里噢。优先级可以自己定义,默认优先级是权值大的元素优先级高。优先队列是一种用途广泛的数据结构,它能巧妙高效的解决很多其他数据结构不容易解决的问题,蒜头君建议大家一定要学好优先队列噢。
这一节我们来学习如何用优先队列来解哈夫曼编码问题。我们在第五章《树和二叉树》最后有介绍哈夫曼编码问题,忘了的同学快回去复习下吧。
对于一个已知的字符串,我们要计算字符串进行哈夫曼编码后的长度,即哈夫曼树的带权路径长度 WPL(Weighted Path Length),也就是每个叶子结点到根结点的距离乘以叶子结点权值结果之和。
我们还是拿第五章的例子来重新描述下问题吧,已知字符串“good good study day day up”,首先统计出每个字符的频率,然后按从大到小顺序排列如下(第二列的字符是空格):
字符 | d | o | y | g | u | a | s | t | p | |
---|---|---|---|---|---|---|---|---|---|---|
编号 | A | B | C | D | E | F | G | H | I | J |
频率 | 5 | 5 | 4 | 3 | 2 | 2 | 2 | 1 | 1 | 1 |
接着我们进行哈夫曼编码,可以得到如下的哈夫曼树。哈夫曼树对应的 WPL 结果为
WPL=5∗2+5∗3+4∗3+3∗3+2∗4+2∗4+2∗4+1∗4+1∗4+1∗4=82
按这个思路,如果我们通过编程来实现的话,不得不手动建一棵哈夫曼树,最后统计每个叶子结点到根的距离。
是不是觉得这样求哈夫曼树的 WPL 非常麻烦呀?这里我们要介绍一种比较方便的方法:
当哈夫曼树上结点总个数大于 1 时,哈夫曼树的 WPL,等于树上除根结点之外的所有结点的权值之和。如果结点总个数为 1,则哈夫曼树的 WPL 即为根结点权值。例如上面的例子里:
WPL=16+10+9+7+5+5+4+5+3+4+2+3+2+2+2+1+1+1=82
应用优先级队列,我们可以快捷地实现这一过程——首先用所有的节点建堆,然后进入循环迭代,每一步都依次取出当前权值最小的两个节点,用它们的加和作为一个新的节点插入到堆中。聪明的你一定能看出,整个操作其实正好跟建立哈夫曼编码树的过程一一对应——而我们只要在操作的过程中,不断地把插入节点的权值累加起来,最后就可以得到我们所要求的 WPL。
是不是觉得很神奇?下面蒜头君就带你来证明下:我们只看结点 E F B 这条分支,如下图所示,结点 a 的权值等于结点 E 和 F 的权值和,结点 b 的权值等于结点 E F B 的权值和,结点 c 依次类推。
当我们把 E a b c 这 4 个结点的权值相加的时候,发现结点 E 的权值一共加了 4 遍,正好等于结点 E 到树根的距离 4。同样,当我们把 F a b c 这 4 个结点的权值相加的时候,发现结点 F 的权值一共加了 4 遍,正好等于结点 F 到树根的距离 4。我们发现所有的叶子结点都满足上述的性质,这就正好解释了我们上一页的结论。
优先队列解哈弗曼编码问题:
#include<iostream>
using namespace std;
class Heap {
private:
int *data, size;
public:
Heap(int length_input) {
data = new int[length_input];
size = 0;
}
~Heap() {
delete[] data;
}
void push(int value) {
data[size] = value;
int current = size;
int father = (current - 1) / 2;
while (data[current] < data[father]) {
swap(data[current], data[father]);
current = father;
father = (current - 1) / 2;
}
size++;
}
int top() {
return data[0];
}
void update(int pos, int n) {
int lchild = 2 * pos + 1, rchild = 2 * pos + 2;
int max_value = pos;
if (lchild < n && data[lchild] < data[max_value]) {
max_value = lchild;
}
if (rchild < n && data[rchild] < data[max_value]) {
max_value = rchild;
}
if (max_value != pos) {
swap(data[pos], data[max_value]);
update(max_value, n);
}
}
void pop() {
swap(data[0], data[size - 1]);
size--;
update(0, size);
}
int heap_size() {
return size;
}
};
int main() {
int n, value, ans = 0;
cin >> n;
Heap heap(n);
for(int i = 1; i <= n; i++) {
cin >> value;
heap.push(value);
}
if(n == 1) {
ans = ans + heap.top();
}
while(heap.heap_size() > 1) {
int a = heap.top();
heap.pop();
int b = heap.top();
heap.pop();
ans = ans + a + b;
heap.push(a + b);
}
cout << ans << endl;
return 0;
}
森林:
森林有两种遍历方法,分别是先序遍历和后序遍历。请注意,森林是没有和树的中序遍历对应的遍历方法的,因为中序遍历只存在于二叉树中,而森林中的树都是一般树,无法区分左右孩子,自然也就无法进行中序遍历了。
注意,不同教材在介绍森林的遍历方法时会有不同的表述,有些教材会将后序遍历称为中序遍历,但实质上遍历方法是一样的。
森林的先序遍历的规则:
- 访问森林中第一棵树的根结点
- 先序遍历森林中第一棵树的子树森林
- 先序遍历森林中,除第一棵树外其余树构成的森林
森林的后序遍历的规则:
- 后序遍历森林中第一棵树的根结点的各子树所构成的森林
- 访问森林中第一棵树的根结点
- 后序遍历森林中除第一棵树外其余树构成的森林
在计算机科学中,并查集(Merge-Find Set),也被称为不相交集合(Disjoint Set),是用于解决若干的不相交集合的如下几种操作的统称:
- MAKE-SET(x):即初始化操作,建立一个只包含元素 x 的集合。
- UNION(x, y):即合并操作,将包含 x 和 y 的集合合并为一个新的集合。
- FIND-SET(x):即查询操作,计算 x 所在的集合。
并查集通常同时指代不相交集合的数据结构及其对应的算法,其在有些教材中的英文名称也叫做 Disjoint Set Union,表示用于求不相交集合并集的相关算法。
通常我们会用有根树来表示集合,树中的每一个结点都对应集合的一个成员,每棵树表示一个集合。
每个成员都有一条指向父结点的边,整个有根树通过这些指向父结点的边来维护。每棵树的根就是这个集合的代表,并且这个代表的父结点是它自己。
通过这样的表示方法,我们将不相交的集合转化为一个森林,也叫不相交森林。接下来我们会介绍,如何通过不相交森林实现并查集的初始化、合并和查询操作。
通常并查集初始化操作是对每个元素都建立一个只包含该元素的集合。这意味着每个成员都是自身所在集合的代表,所以我们只需要将所有成员的父结点设为它自己就好了。
在不相交森林中,并查集的查询操作,指的是查找出指定元素所在有根树的根结点是谁。我们可以通过每个指向父结点的边回溯到结点所在有根树的根,也就是对应集合的代表元素。
并查集的合并操作需要用到查询操作的结果。合并两个元素所在的集合,需要首先求出两个元素所在集合的代表元素,也就是结点所在有根树的根结点。接下来将其中一个根结点的父亲设置为另一个根结点。这样我们就把两棵有根树合并成一棵了。
并查集的合并操作非常关键,下面我们举个小例子来说明合并操作对应的不相交森林的状态变化。
如上图所示,图(a)为两个合并前的集合对应的不相交森林,两个集合对应的有根树的根分别是 c 和 f。我们将两个集合进行合并,就会得到图(b)所示的新集合,集合对应的有根树的根为 f。
并查集的查询操作最坏情况下的时间复杂度为 O(n),其中 n 为总元素个数。最坏情况发生时,每次合并对应到森林上都是一个点连到一条链的一端。此时如果每次都查询链的最底端,也就是最远离根的位置的元素时,复杂度便是 O(n) 了。
为了改善时间效率,可以通过启发式合并方法,将包含较少结点的树接到包含较多结点的树根上,可以防止树退化成一条链。另外,我们也可以通过路径压缩的方法来进一步减少均摊复杂度。同时使用这两种优化方法,可以将每次操作的时间复杂度优化至接近常数级。
是不是觉得并查集很强大呀,快随蒜头君一起,深入学习并查集吧,加油噢~
并查集的初始化:#include <iostream>
using namespace std;
class DisjointSet {
private: int *father;
public : DisjointSet(int size) {
father = new int[size];
for(int i = 0; i < size; i++ ) {
father[i] = i;
}
}
~DisjointSet() {
delete[] father;
}
};
int main() {
DisjointSet dsu(100);
return 0;
}
查询:
#include <iostream>
using namespace std;
class DisjointSet {
private:
int *father;
public:
DisjointSet(int size) {
father = new int[size];
for (int i = 0; i < size; ++i) {
father[i] = i;
}
}
~DisjointSet() {
delete[] father;
}
int find_set(int node) {
if(father[node] != node) {
return find_set(father[node]);
}
return node;
}
};
int main() {
DisjointSet dsu(100);
return 0;
}
并查集的合并:
#include <iostream>
using namespace std;
class DisjointSet {
private:
int *father;
public:
DisjointSet(int size) {
father = new int[size];
for (int i = 0; i < size; ++i) {
father[i] = i;
}
}
~DisjointSet() {
delete[] father;
}
int find_set(int node) {
if (father[node] != node) {
return find_set(father[node]);
}
return node;
}
bool merge(int node1, int node2) {
int ancestor1 = find_set(node1);
int ancestor2 = find_set(node2);
if(ancestor1 != ancestor2) {
father[ancestor1] = ancestor2;
return true;
}
return false;
}
};
int main() {
DisjointSet dsu(100);
int m, x, y;
cin >> m;
for(int i = 0; i < m; i++) {
cin >> x >> y;
bool ans = dsu.merge(x, y);
if(ans) {
cout << "success" << endl;
} else {
cout << "failed" << endl;
}
}
return 0;
}
按秩合并优化:
#include <iostream>
using namespace std;
class DisjointSet {
private:
int *father, *rank;
public:
DisjointSet(int size) {
father = new int[size];
rank = new int[size];
for (int i = 0; i < size; ++i) {
father[i] = i;
rank[i] = 0;
}
}
~DisjointSet() {
delete[] father;
delete[] rank;
}
int find_set(int node) {
if (father[node] != node) {
return find_set(father[node]);
}
return node;
}
bool merge(int node1, int node2) {
int ancestor1 = find_set(node1);
int ancestor2 = find_set(node2);
if (ancestor1 != ancestor2) {
if(rank[ancestor1] > rank[ancestor2]) {
swap(ancestor1, ancestor2);
}
father[ancestor1] = ancestor2;
rank[ancestor2] = max(rank[ancestor1] + 1, rank[ancestor2]);
return true;
}
return false;
}
};
int main() {
DisjointSet dsu(100);
int m, x, y;
cin >> m;
for (int i = 0; i < m; ++i) {
cin >> x >> y;
bool ans = dsu.merge(x, y);
if (ans) {
cout << "success" << endl;
} else {
cout << "failed" << endl;
}
}
return 0;
}
路径压缩优化:
#include <iostream>
using namespace std;
class DisjointSet {
private:
int *father, *rank;
public:
DisjointSet(int size) {
father = new int[size];
rank = new int[size];
for (int i = 0; i < size; ++i) {
father[i] = i;
rank[i] = 0;
}
}
~DisjointSet() {
delete[] father;
delete[] rank;
}
int find_set(int node) {
if (father[node] != node) {
father[node] = find_set(father[node]);
}
return father[node];
}
bool merge(int node1, int node2) {
int ancestor1 = find_set(node1);
int ancestor2 = find_set(node2);
if (ancestor1 != ancestor2) {
if (rank[ancestor1] > rank[ancestor2]) {
swap(ancestor1, ancestor2);
}
father[ancestor1] = ancestor2;
rank[ancestor2] = max(rank[ancestor1] + 1, rank[ancestor2]);
return true;
}
return false;
}
};
int main() {
DisjointSet dsu(100);
int m, x, y;
cin >> m;
for (int i = 0; i < m; ++i) {
cin >> x >> y;
bool ans = dsu.merge(x, y);
if (ans) {
cout << "success" << endl;
} else {
cout << "failed" << endl;
}
}
return 0;
}
图:
到底什么是图?图是由一系列顶点和若干连结顶点集合内两个顶点的边组成的数据结构。数学意义上的图,指的是由一系列点与边构成的集合,这里我们只考虑有限集。通常我们用 G=(V,E) 表示一个图结构,其中 V 表示点集,E 表示边集。
在顶点集合所包含的若干个顶点之间,可能存在着某种两两关系——如果某两个点之间的确存在这样的关系的话,我们就在这两个点之间连边,这样就得到了边集的一个成员,也就是一条边。对应到社交网络中,顶点就是网络中的用户,边就是用户之间的好友关系。
如果用边来表示好友关系的话,对于微信这种双向关注的社交网络没有问题,但是对于微博这种单向关注的要如何表示呢?
于是引出了两个新的概念:有向边和无向边。
简而言之,一条有向边必然是从一个点指向另一个点,而相反方向的边在有向图中则不一定存在;而有的时候我们并不在意构成一条边的两个顶点具体谁先谁后,这样得到的一条边就是无向边。就像在微信中,A 是 B 的好友,那 B 也一定是 A 的好友;而在微博中,A 关注 B 并不意味着 B 也一定关注 A。
对于图而言,如果图中所有边都是无向边,则称为无向图,反之称为有向图。
简而言之,无向图中的边是“好友”,而有向图中的边是“关注”。一般而言,我们在数据结构中所讨论的图都是有向图,因为有向图相比无向图更具有代表性。
实际上,无向图可以由有向图来表示。如果 AB 两个点之间存在无向边的话,那用有向图也可以表示为:AB 两点之间同时存在 A 到 B 与 B 到 A 两条有向边。
仍然以社交网络举例:虽然微博中并不存在明确定义的好友关系,但是一般情况下,如果你和另一个 ID 互相关注的话,那么我们也可以近似认为,你和 TA 是好友。
现在,我们已经了解了图的定义和有向图、无向图的概念。在接下来的课程中,我们会继续学习图的一些基本概念,并学习如何实现图结构的存储和输出。
这一节课我们来学习图的几个常用概念。有很少边或弧(如 e < nlogn , e 指边数, n 指点数)的图称为 稀疏图 ,反之称为 稠密图 。对应到微博里,如果在一个圈内,大家都互相关注,则我们可以认为该关系图是一个稠密图,如果只有几个人关注了别人,则我们可以认为这是一个稀疏图。顶点的度是指依附于某个顶点的边数。下图是电视剧《琅琊榜》的人物关系图,从图上我们发现和顶点“萧景琰”直接相关的有 4 条关系,则我们称该顶点的度为 4。相应的,我们称顶点“萧景睿”的度为 2。
在有向图中,我们需要学习顶点的入度和出度这两个概念。顶点的入度是指以顶点为弧头的弧的数目,也就是以该顶点为终点的弧的数目;顶点的出度是指以顶点为弧尾的弧的数目,也就是以该顶点为起点的弧的数目。需要注意的是,在有向图里,顶点的度为入度与出度之和。
这一节我们来学习两个常见的图的存储结构——邻接矩阵和邻接表。
什么是邻接矩阵呢?所谓邻接矩阵存储结构就是用一维数组存储图中顶点的信息,用矩阵表示图中各顶点之间的邻接关系。
对于有 n 个顶点的图 G=(V,E) 来说,我们可以用一个 n∗n 的矩阵 A 来表示 G 中各顶点的相邻关系,如果 vi 和 vj之间存在边(或弧),则 A[i][j]=1,否则 A[i][j]=0。下图为有向图 G1 以及对应的邻接矩阵。
下图为无向图 G2 以及对应的邻接矩阵。
图的邻接矩阵是唯一的,矩阵的大小只与顶点个数 N 有关,是一个 N∗N 的矩阵。前面我们已经介绍过,在无向图里,如果顶点 vi 和 vj 之间有边,则可认为顶点 vi 到 vj 有边,同时顶点 vj 到 vi 也有边。对应到邻接矩阵里,则有 A[i][j]=A[j][i]=1 。因此我们可以发现,无向图的邻接矩阵是一个对称矩阵。
在邻接矩阵上,我们可以直观的看出两个顶点之间是否有边(或弧),并能容易求出每个顶点的度,入度和出度。
这里我们以图 G1 为例,演示下如何利用邻接矩阵计算顶点的入度和出度。顶点的出度,即为邻接矩阵上点对应行上所有值的总和,比如顶点 1 对应的出度即为 0+1+1+1=3;而每个点的入度即为点对应列上所有值的总和,比如顶点 3对应的入度即为 1+0+0+1=2。
学习了邻接矩阵,我们再来学习邻接表吧。邻接表是图的一种顺序存储与链式存储相结合的存储方法。我们给图 G 中的每个顶点建立一个单链表,第 i 个单链表中的结点表示依附于顶点 vi 的边(对于有向图是以 vi 为起点的弧)。所有单链表的表头结点都存储在一个一维数组中,以便于顶点的访问。下图为图 G1 对应的邻接表。
在无向图的邻接表中,顶点 vi 的度为第 i 个单链表中的结点数;而在有向图中,第 i 个单链表中的结点数表示的是顶点 vi的出度,如果要求入度,则要遍历整个邻接表。
另外,在邻接表中,我们很容易就能知道某一顶点和哪些顶点相连接。
学习完两种存储结构,可能你会有这样的疑问:那我们什么时候用邻接矩阵,什么时候用邻接表呢?
我们可以看到,邻接矩阵存储结构最大的优点就是简单直观,易于理解和实现。其适用范围广泛,有向图、无向图、混合图、带权图等都可以直接用邻接矩阵表示。另外,对于很多操作,比如获取顶点度数,判断某两点之间是否有连边等,邻接矩阵都可以在常数时间内完成。
然而,它的缺点也是显而易见的:从以上的例子我们可以看出,对于一个有 n 个顶点的图,邻接矩阵总是需要 n2 的存储空间。当边数很少的时候,就会造成空间的浪费。
因此,具体使用哪一种存储方式,要根据图的特点来决定:如果是稀疏图,我们一般用邻接表来存储,这样可以节省空间;如果是稠密图,考虑到邻接表中要附加链域,我们一般用邻接矩阵来存储。
邻接矩阵的构造:
#include <iostream>
#include <cstring>
using namespace std;
class Graph {
private:
int **mat;
int n;
public:
Graph(int input_n) {
n = input_n;
mat = new int*[n];
for(int i = 0; i < n; i++) {
mat[i] = new int[n];
memset(mat[i], 0, sizeof(int) * n);
}
}
~Graph() {
for(int i = 0; i < n; i++) {
delete[] mat[i];
}
delete[] mat;
}
};
int main() {
Graph g(100);
return 0;
}
邻接矩阵的使用:
#include <iostream>
#include <cstring>
using namespace std;
class Graph {
private:
int **mat;
int n;
public:
Graph(int input_n) {
n = input_n;
mat = new int*[n];
for (int i = 0; i < n; ++i) {
mat[i] = new int[n];
memset(mat[i], 0, sizeof(int) * n);
}
}
~Graph() {
for (int i = 0; i< n; ++i) {
delete[] mat[i];
}
delete[] mat;
}
void insert(int x, int y) {
mat[x][y] = 1;
}
void output() {
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
cout << mat[i][j] << " ";
}
cout << endl;
}
}
};
int main() {
int n, m, x, y;
cin >> n >> m;
Graph g(n);
for (int i = 0; i < m; ++i) {
cin >> x >> y;
g.insert(x, y);
}
g.output();
return 0;
}
邻接表的构造:
#include <iostream>
using namespace std;
class LinkedListNode {
public:
int vertex;
LinkedListNode *next;
LinkedListNode(int vertex_input) {
vertex = vertex_input;
next = NULL;
}
};
class LinkedList {
public:
LinkedListNode *head;
LinkedList() {
head = NULL;
}
~LinkedList() {
while (head != NULL) {
LinkedListNode *delete_node = head;
head = head->next;
delete delete_node;
}
}
void insert(int vertex) {
LinkedListNode *node = new LinkedListNode(vertex);
node->next = head;
head = node;
}
};
class Graph {
private: LinkedList *edges;
int n;
public: Graph(int input_n) {
n = input_n;
edges = new LinkedList[n];
}
~Graph() {
delete[] edges;
}
};
int main() {
Graph g(100);
return 0;
}
邻接表的使用:
#include <iostream>
using namespace std;
class LinkedListNode {
public:
int vertex;
LinkedListNode *next;
LinkedListNode(int vertex_input) {
vertex = vertex_input;
next = NULL;
}
};
class LinkedList {
public:
LinkedListNode *head;
LinkedList() {
head = NULL;
}
~LinkedList() {
while (head != NULL) {
LinkedListNode *delete_node = head;
head = head->next;
delete delete_node;
}
}
void insert(int vertex) {
LinkedListNode *node = new LinkedListNode(vertex);
node->next = head;
head = node;
}
};
class Graph {
private:
LinkedList *edges;
int n;
public:
Graph(int input_n) {
n = input_n;
edges = new LinkedList[n];//n条 链表
}
~Graph() {
delete[] edges;
}
void insert(int x, int y) {
edges[x].insert(y);
}
void output() {
for(int i = 0; i < n; i++) {
cout << i << ":";
for(auto j = edges[i].head; j != NULL; j = j->next) {
cout << j->vertex << " ";
}
cout << endl;
}
}
};
int main() {
int n, m, x, y;
cin >> n >> m;
Graph g(n);
for (int i = 0; i < m; ++i) {
cin >> x >> y;
g.insert(x, y);
}
g.output();
return 0;
}
图的遍历:
在上一章课程里,我们介绍了图和图的两种存储方法。我们知道,现实生活中,一系列个体与个体之间存在的两两关系,可以抽象成图这种数据结构来表示。现在,让我们来讨论一个有趣的例子:维基百科的“终极真理之路”。
维基百科大家都用过,但是你有没有注意到它有一个特别的“玩法”呢?具体来说,如果你每点开任意一个词条,比如“WeChat(微信)”,然后每次只能点击词条中的第一个链接,那么在经历了一定的跳转次数之后,你最后一定能抵达“Philosophy(哲学)”这一词条。这个有趣的现象也称为“维基百科的终极真理”。
和人际交往之间的六度分割原理类似,维基百科遵循的是一种三度分割原理:两个词条之间的最短路径普遍为三个——比如从“我”到“奥巴马”:
I → letter → Federal Bureau of Investigation → Barack Obama
再比如从“微软”到“理查德.F.赫克(2010年诺贝尔化学奖得主)”:
Microsoft → Intel → Chemist → Richard F.Heck
另外,有兴趣的同学可以访问这个网站:Degree of Wikipedia,它可以求出任意两个词条之间的最短路径。
那么,对于计算机来说,怎样才能知道从一个词条出发可以跳转到哪些词条呢?整个维基百科可以抽象成一个图结构——每一个词条都是一个节点,而指向其他词条的链接就是图中的边。运用计算机求解这一问题,需要用到我们接下来要介绍的,图的遍历算法。
什么是图的遍历呢?从图的某个顶点出发,沿图中的路径依次访问图中所有顶点,并且使得图中所有顶点都恰好被访问一次,这一过程即为图的遍历。需要注意的是,接下来讨论图的遍历时,都是特指在一个连通图上进行遍历。
图有两种最常见的遍历算法:广度优先搜索(BFS)和深度优先搜索(DFS),在后面的课程中我们会详细介绍它们。
还记得我们之前讲过的二叉树和森林的各种遍历方法么?从思路上讲,图的遍历和树或森林的遍历的核心思路很像:都是化繁为简。
大家可以回顾一下,此前我们通过对二叉树进行遍历,可以将二叉树转换为一个线性序列。这就是化繁为简的思路:把树这种非线性结构转化为线性序列。这样一来,二叉树的很多问题都可以转化到线性序列上进行求解。
我们都知道,线性序列是树或森林的一个特例,而树或森林则是图结构的一个特例。仿照树的遍历,我们在对图进行遍历的过程中,首先将图这种复杂的非线性结构,转化为图的一种特例,就是一棵树——生成树(也叫支撑树)。
这样的一棵生成树,具有两个重要的性质:其一,它的根节点便是我们对图进行遍历时的起始顶点;其二,它包含图中的所有顶点。对于一个图,可能会有很多种合法的生成树哦。接下来,做一道习题来巩固生成树的概念吧。
这一节我们来学习图的第一种遍历方法——深度优先搜索(Depth-First-Search,简称 DFS)。这是一种常见的用于遍历或搜索 树或者图 的算法。我们首先来看看深度优先搜索算法的具体过程吧:
开始我们假设图上所有顶点都未被访问,选择图中任一顶点,开始执行以下操作:
1 访问当前顶点 v,并将顶点标为已访问;
2 遍历与顶点 v 相邻的所有顶点 c,然后对顶点 v 所有尚未被访问的相邻顶点 c,递归地执行第一步操作;如果当前顶点已经没有未访问的相邻顶点了,则说明该分支搜索结束,沿通路回溯到顶点 v。
3 此时如果还有相邻顶点没有被访问,则从该顶点继续开始深度优先搜索。直到所有顶点都被访问。
从操作方法上我们可以看出,深度优先搜索遍历算法,总是沿着图的某一深度进行遍历,尽可能深的搜索与当前相邻的顶点——如果相邻的顶点都已被访问则回溯到上一层,直至所有顶点都已被访问。
下面我们就用一个小例子模拟下深度优先搜索的遍历过程吧。
仔细分析后我们发现,对一个连通图进行深度优先搜索,其实是在对该图的一个生成树进行搜索,这里我们把这棵生成树称为深度优先搜索树。如下图就是刚才例子的一个深度优先搜索树。
对于算法的具体实现,因深度优先搜索的优先遍历深度更大的顶点,所以我们可以借助栈这一数据结构来实现:
1 将要访问的第一个顶点 v 入栈,然后首先对其进行访问;
2 将顶点 v 出栈,依次将与顶点 v 相邻且未被访问的顶点 c 压入栈中;
3 重复第一步操作,直至栈为空。
为了方便,我们通常以递归的形式实现深度优先搜索。在后面课程里,我们会详细教大家如何具体实现噢。
邻接表深度优先搜索的实现:
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
class Graph {
private:
int n; //!< 顶点数量
vector<int> *edges; //!< 邻接表
bool *visited;
public:
Graph(int input_n) {
n = input_n;
edges = new vector<int>[n];
visited = new bool[n];
memset(visited, 0, n);
}
~Graph() {
delete[] edges;
delete[] visited;
}
void insert(int x, int y) {
edges[x].push_back(y);
edges[y].push_back(x);
}
void dfs(int vertex) {
cout << vertex << endl;
visited[vertex] = true;
for(int adj_vertex: edges[vertex]) {
if(!visited[adj_vertex]) {
dfs(adj_vertex);
}
}
}
};
int main() {
int n, m, k;
cin >> n >> m;
Graph g(n);
for (int i = 0; i < m; ++i) {
int x, y;
cin >> x >> y;
g.insert(x, y);
}
cin >> k;
g.dfs(k);
return 0;
}
这一课我们来学习图的另一种遍历方法——广度优先搜索(Breadth-First-Search,简称 BFS)。这是一种连通图的常用遍历策略,通常用于求起点到各点的最短路径,以及求两点之间的最优路径等问题。首先我们先来看看广度优先搜索的具体方法吧:
对于一个连通图,我们假设一开始所有顶点均未被访问,广度优先搜索的主要操作如下:
1 选择图中任意一个顶点 v 作为起点进行访问,并将顶点 v 标为已访问。
2 遍历并访问与顶点 v 相邻且未被访问的所有顶点 c1, c2, …, ck;接着遍历并访问与顶点 c1, c2, ..., ck 相邻且未被访问的顶点——也就是依次访问所有相邻顶点的相邻顶点;以此类推,直到所有顶点均被访问。
从遍历过程中,我们可以看到,广度优先搜索,总是从某一起点出发,逐层进行搜索。每一层的顶点到起点的边数都是一样的。利用这一思想,正好解释了为什么广度优先搜索可以求出图中一点到各点的最短距离,以及求某两点之间的最优路径等问题。
下面我们就用一个小例子模拟下广度优先搜索的遍历过程吧。
同样我们可以从遍历过程中发现,对一个连通图进行广度优先搜索,其实是在对该图的一个生成树进行搜索,这里我们把这个生成树称为广度优先搜索树。如下图就是刚才例子的一棵广度优先搜索树。
对于算法的具体实现,结合队列先进先出的特性,我们可以借助队列这一数据结构来实现广度优先搜索:
1 任意选择一个顶点 v 作为起点,加入队列;
2 访问队首元素 v 并标记,将其从队列中删除;
3 遍历与顶点 v 相邻且未被访问的所有顶点 c1, c2, …, ck,并依次加入到队列中;
4 重复第二步和第三步操作,直到队列为空。
在后面的课程里,我们会详细教大家如何实现广度优先搜索噢。
邻接表实现的广度优先搜索:#include <iostream>
#include <vector>
#include <cstring>
#include <queue>
using namespace std;
class Graph {
private:
int n;
bool *visited;
vector<int> *edges;
public:
Graph(int input_n) {
n = input_n;
edges = new vector<int>[n];
visited = new bool[n];
memset(visited, 0, n);
}
~Graph() {
delete[] edges;
delete[] visited;
}
void insert(int x, int y) {
edges[x].push_back(y);
edges[y].push_back(x);
}
void bfs(int start_vertex) {
queue<int> bfs_queue;
bfs_queue.push(start_vertex);
visited[start_vertex] = true;
while(!bfs_queue.empty()) {
int vertex = bfs_queue.front();
cout << vertex << endl;
bfs_queue.pop();
for(int adj_vertex: edges[vertex]) {
if(!visited[adj_vertex]) {
visited[adj_vertex] = true;
bfs_queue.push(adj_vertex);
}
}
}
}
};
int main() {
int n, m, k;
cin >> n >> m;
Graph g(n);
for (int i = 0; i < m; ++i) {
int x, y;
cin >> x >> y;
g.insert(x, y);
}
cin >> k;
g.bfs(k);
return 0;
}
图论算法入门:
这一章我们来学习一些图的更高级、也更有趣的算法。
首先,我们来介绍一个概念:子图(subgraph)。若一个图的顶点集和边集分别是另一图的顶点集的子集和边集的子集,则称该图为另一图的子图。
换句话说,从一个图里选出一部分顶点和边,只要确保选择的边对应的两个顶点也都被选择,那么所有选出的顶点和边组成的图就是原图的子图。
就像在一个社交网络中,同班同学的帐号之间的关系就组成了整个社交网络的一个子图。
接下来,我们介绍一个概念:连通。在无向图中,如果有从顶点 v 到顶点 w 的路径存在,则称 v 和 w 是连通的。若图 G中任意两个顶点都是连通的,则称图 G 为连通图,否则成为非连通图。
若图 G 的子图 Gs 是连通的,我们就称子图 Gs 是图 G 的连通子图。如果对于图 G 的一个连通子图 Gs,不存在图 G 的其他连通子图 Gmax 满足:Gs 是 Gmax 的子图。则子图 Gs 是图 G 的极大连通子图,也就是图 G 的连通分量。
具有7个顶点的有向图至少应有多少条边才可能成为一个强连通图?强连通图必须从任何一点出发都可以回到原处,每个节点至少要一条出路(单节点除外)至少有n条边,正好可以组成一个环
如何求解无向图的连通分量呢?这要用到我们本章介绍的第一个图论算法:FloodFill 算法。
FloodFill 算法通常译作“洪水灌溉法”,算法通过给图中的顶点染色,最终使得同一个连通分量的顶点颜色相同,不同连通分量的顶点颜色不同。算法的描述如下:
1. 找到一个没有染色的顶点,将其染为新的颜色 Colornew,如果没有则算法结束。
2. 初始化一个空的队列,并将第一步的顶点插入队列。
3. 不断获得队首元素的值并弹出,将和队首元素相邻的未染色顶点染为 Colornew,并将其加入队列。
4. 重复执行第一步,直到所有顶点都被染色,算法结束。
下图是 FloodFill 算法的演示:
可以看出,最终整张图被染成了三种颜色,也就是说图中有三个连通分量。
根据每个顶点染色的结果,我们就可以求出每个连通分量包含的顶点信息。
FloodFill 的时间复杂度是 O(V+E),其中广度优先遍历的部分可以替换成深度优先遍历,复杂度是一样的。通常考虑到递归调用的时间开销,往往广度优先遍历的效率要更高一些。
在后面的课程中,我们会继续巩固连通分量的概念,并动手实现 FloodFill 算法。这一章我们还将学习并实现很多有趣的图论算法,比如最小生成树的 Prim 算法、单源最短路的 Dijkstra 算法等。加油!
选项中有若干关于无向图的连通子图、连通分量的概念,请从其中选出 正确 的选项。
有 n 座城市,现要给城市间铺设高铁,使得任意两座城市之间都可以高铁到达。现已知任意两座城市之间铺设高铁的费用,求问如何铺设可以使得总费用最小。
这就是一个经典的最小生成树问题。首先我们可以把该场景看成是一个带权图,城市就是图中的顶点,边的权值就是城市间铺设高铁的费用。另外我们可以知道 n 个顶点,只需要 n−1 条边就能让任意两点连通。
那么问题就转化成了:如何从一个带权图中抽出一棵生成树,使得边权值和最小,这棵生成树就叫做最小生成树。常见的求解最小生成树的算法有 Prim 算法和 Kruskal 算法,在后面的课程里,我们会详细介绍这两种算法。
我们先来学习 Prim 算法。首先我们定义带权图 G 的顶点集合为 V,接着我们再定义最小生成树的顶点集合为 U,初始集合 U 为空。接着执行以下操作:
-
首先我们任选一个顶点 x,加入集合 U,并记录每个顶点到当前最小生成树的最短距离。
-
选择一个距离当前最小生成树最近的、且不属于集合 U 的顶点 v(如果有多个顶点 v,任选其一即可),将顶点 v 加入集合 U,并更新所有与顶点 v 相连的顶点到当前最小生成树的最短距离。
-
重复第二步操作,直至集合 U 等于集合 V。
最小生成树构造完毕,集合 U 记录了最小生成树的所有边。
下面我们再用一个小例子来模拟下 Prim 算法吧。
通过模拟,最终我们可以得到该图对应的最小生成树,如下图所示:
从图中我们可以算出最小权值和为:
9+31+19+51=110
分析算法过程,我们可以发现,Prim 算法的思想类似贪心策略,每次都会选择一条与当前最小生成树相连且边权值最小的点。Prim 算法的时间复杂度为 O(V2),V 为图 G 顶点总个数,如果加上堆优化的话,可以把时间复杂度降到 O(VlogV+E),其中 E 为图 G 的总边数。Prim 算法一般应用于边较为稠密的图,也就是顶点较少、而边较多的图。
prim算法的实现:
#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
const int INF = 0x3f3f3f3f;
struct Edge {
int vertex, weight;
};
class Graph {
private:
int n;
bool * visited;
vector<Edge> * edges;
public:
int * dist;
Graph (int input_n) {
n = input_n;
edges = new vector<Edge>[n];
dist = new int[n];
visited = new bool[n];
memset(visited, false, n * sizeof(bool));
memset(dist, 0x3f, n * sizeof(int));
}
~Graph() {
delete[] dist;
delete[] visited;
delete[] edges;
}
void insert(int x, int y, int weight) {
edges[x].push_back(Edge{y, weight});
edges[y].push_back(Edge{x, weight});
}
int prim(int v) {
int total_weight = 0;
dist[v] = 0;
for(int i = 0; i < n; i++) {
int min_dist = INF, min_vertex;
for(int j = 0; j < n; j++) {
if(!visited[j] && dist[j] < min_dist) {
min_dist = dist[j];
min_vertex = j;
}
}
total_weight += min_dist;
visited[min_vertex] = 1;
for(Edge &j: edges[min_vertex]) {
if(!visited[j.vertex] && j.weight < dist[j.vertex]) {
dist[j.vertex] = j.weight;
}
}
}
return total_weight;
}
};
int main() {
int n, m;
cin >> n >> m;
Graph g(n);
for (int i = 0; i < m; i++) {
int a, b, c;
cin >> a >> b >> c;
g.insert(a, b, c);
}
cout << g.prim(0) << endl;
return 0;
}
Kruskal算法:
这一课我们来学习最小生成树的另一种算法——Kruskal 算法。首先我们定义带权图 G 的边集合为 E,接着我们再定义最小生成树的边集合为 T,初始集合 T 都为空。接着执行以下操作:
-
首先,我们把图 G 看成一个有 n 棵树的森林,图上每个顶点对应一棵树。
-
接着,我们将边集合 E 的每条边,按权值从小到大进行排序,
-
依次遍历每条边 e=(u,v),我们记顶点 u 所在的树为 Tu,顶点 v 所在的树为 Tv,如果 Tu 和 Tv 不是同一棵树,则我们将边 e 加入集合 T,并将两棵树 Tu 和 Tv 进行合并。
算法执行完毕后,集合 T 记录了最小生成树的所有边。
下面我们再用一个小例子来模拟下 Kruskal 算法吧。
通过模拟,最终我们可以得到该图对应的最小生成树,如下图所示:
从图中我们可以算出最小权值和为:
9+42+31+19=121
仔细分析算法,我们可以发现,Kruskal 算法也是采用了贪心的策略,每次都会选择一条两个顶点不在同一棵树且权值最小的边加入集合。Kruskal 算法的时间复杂度为 O(ElogE),E 为图 G 的总边数,所以Kruskal 算法一般应用于较为稀疏的图,也就是顶点较多、而边较少的图。
这一课我们来学习图论的另一个问题——最短路问题。什么是最短路问题呢?我们先来看这样一个问题:
有 n 座城市,已知任意两座城市之间的距离,现在要分别求出城市 A 到其他 n−1 座城市的最短路径,也就是求所经过的距离和的最小值。
这是一个经典的单源最短路问题,即求一起点到其余各个顶点的最短路径问题。
首先我们可以把该场景看成是一个带权图,把 n 个城市看成 n 个顶点,把两座城市之间的距离看成是两个顶点之间的边权值,这样问题就转化成了求顶点 A 到其余 n−1 个顶点的最短路径。
Dijkstra 算法是常见的求解单源最短路问题的算法,我们将在后面详细讲述 Dijkstra 算法。
我们先来看看 Dijkstra 算法的具体过程:
我们定义带权图 G 所有顶点的集合为 V,接着我们再定义已确定最短路径的顶点集合为 U,初始集合 U 为空。接着执行以下操作:
-
首先我们将起点 x 加入集合 U,并在数组 A 中记录起点 x 到各个点的最短路径(如果顶点到起点 x 有直接相连的边,则最短路径为边权值,否则为一个极大值)。
-
从数组 A 中选择一个距离起点 x 最近的、且不属于集合 U 的顶点 v(如果有多个顶点 v,任选其一即可),将顶点 v 加入集合 U,并更新所有与顶点 v 相连的顶点到起点 x 的最短路径。
-
重复第二步操作,直至集合 U 等于集合 V。
算法结束,数组 A 记录了起点 x 到其余 n−1 个点的最短路径。
下面我们再用一个小例子来模拟下 Dijkstra 算法吧。
通过模拟,最终我们可以得到起点 a 到各点的最短路径分别为:
l:5(a−>l)
e:8(a−>l−>e)
r:7(a−>l−>r)
b:9(a−>l−>e−>b)
仔细分析算法,我们可以发现,Dijkstra 算法和前面讲解的 Prim 算法很相像,都是从一个点开始,每次确定一个点并完成更新,重复操作直至 n 个点都确定为止。Dijkstra 算法的时间复杂度为 O(V2+E),V 为顶点总个数,E 为总边数。如果利用堆进行优化,可以将时间复杂度优化 O(VlogV+E),是最坏情况下最优的单源最短路算法。
需要注意的是,Dijkstra 不适用于有边权为负数的情况哦,否则会影响算法的正确性。
Dijkstra算法的实现: #include <iostream>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
const int INF = 0x3f3f3f3f;
struct Edge {
int vertex, weight;
};
class Graph {
private:
int n;
vector<Edge> * edges;
bool * visited;
public:
int * dist;
Graph (int input_n) {
n = input_n;
edges = new vector<Edge>[n];
dist = new int[n];
visited = new bool[n];
memset(visited, 0, n);
memset(dist, 0x3f, n * sizeof(int));
}
~Graph() {
delete[] dist;
delete[] edges;
delete[] visited;
}
void insert(int x, int y, int weight) {
edges[x].push_back(Edge{y, weight});
edges[y].push_back(Edge{x, weight});
}
void dijkstra(int v) {
dist[v] = 0;
for(int i =0; i < n; i++) {
int min_dist = INF, min_vertex;
for(int j = 0; j < n; j++) {
if(!visited[j] && dist[j] < min_dist) {
min_dist = dist[j];
min_vertex = j;
}
}
visited[min_vertex] = 1;
for(Edge &j: edges[min_vertex]) {
if(min_dist + j.weight < dist[j.vertex]) {
dist[j.vertex] = min_dist + j.weight;
}
}
}
}
};
int main() {
int n, m;
cin >> n >> m;
Graph g(n);
for (int i = 0; i < m; i++) {
int a, b, c;
cin >> a >> b >> c;
g.insert(a, b, c);
}
g.dijkstra(0);
for (int i = 0; i < n; i++) {
cout << i << ": " << g.dist[i] << endl;
}
return 0;
}