使用建议:建议使用 gedit 软件查看本笔记,可以让代码中的关键字、注释等高亮显示,增强可读性;
另外建议把 gedit 软件首选项里的制表位设置为 4(默认为 8),代码中的缩进就会更合理;字体建议用 13 号。
《目录》
DAY01---逻辑结构,物理结构,数据结构基本运算,堆栈,顺序表堆栈:任意进制转换(练习),链式表堆栈
DAY02---队列,顺序表队列,链式表队列,堆栈实现队列效果,链表,练习:实现一个单向线性链表,二叉树,有序二叉树
DAY03---算法,冒泡排序,插入排序,选择排序,快速排序,自写 qsort,归并排序,线性查找,二分查找
**************************************************************
DAY01-MIN
1. 逻辑结构
1)集合结构(集):结构中的元素除了同属一个集以外,没有任何联系;
2)线性结构(表):结构中的元素具有一对一的前后关系;
特点:同一性,有穷性,有序性
3)树型结构(树):结构中的元素具有一对多的父子关系;
4)网状结构(图):结构中的元素具有多对多的关系,交叉映射;
问:三种基本的数据模型 ?
答:按照数据结构类型的不同,将数据模型划分为层次模型、网状模型和关系模型。
2. 物理结构
1)顺序结构(数组):结构中的元素存放在一组连续的地址空间中;
优点:随机访问方便;
缺点:需要连续内存,空间利用率低;为保证地址连续性,进行插入、删除的时候,会带动大量数据移动;
2)链式结构(链式):结构中的元素存放在彼此独立的地址空间中,
每个独立的地址空间被称为节点,节点中除了保存数据以外,还保存了另外一个或者几个相关节点的地址和本节点的相关属性;
优点:充分利用零散碎片,空间利用率高;插入、删除方便;
缺点:随机访问不方便;
3. 物理结构和逻辑结构关系
表 树 图
顺序 数组 顺序树 复合结构
链式 链表 链式树 复合结构
4. 数据结构基本运算
1)创建与销毁
2)插入与删除
3)获取与修改
4)排序与查找
5. 堆栈
1)基本特征:后进先出;
2)基本操作:压入(push),弹出(pop);
3)实现要点:a.初始化空间,b.栈顶指针(不一定是指针,数组下标也可以),c.判断空满;
6. 基于顺序表的堆栈:实现对一个整数进行任意进制的转换
class Stack {
public:
// 构造中分配内存
Stack(size_t size = 10) : m_array(new int[size]),
m_size(size), m_top(0) {}
// 析构内存
~Stack() {
if(m_array) {
delete[] m_array;
m_array = 0;
}
}
// 压入
void push(int data) {
if (full()) // 判满
throw OverFlow();
m_arrary[m_top++] = data;
}
// 弹出
int pop() {
if (empty())
throw UnderFlow();
return m_arrary[--m_top];
// 这里是前--,不是后--,如果是后--,则弹出的是 m_top 的下一个元素,不存在;
}
// 判满
bool full() const {
return m_top >= m_size;
}
// 判空
bool empty() const {
return ! m_top;
}
private:
// 上溢异常(内部类实现)
class OverFlow : public exception {
const char* what() const throw() {
return "堆栈上溢!";
}
};
// 下溢异常
class UnderFlow : public exception {
const char* what() const throw() {
return "堆栈下溢!";
}
};
int* m_array; // 数组
size_t m_size; // 最大容量
size_t m_top; // 栈顶
};
// 实现进制转换
void printb(unsigned int numb, int base) {
Stack stack(256);
do { // 用 do 排除 0 的情况
stack.push(num % base);
} while (num /= base);
while(! stack.empty()) {
int digit = stack.pop();
if(digit < 10)
cout << digit;
else
cout << char(digit - 10 + 'A');
}
cout << endl;
}
int main() {
// 测试顺序堆栈
try {
Stack stack;
for(int i = 0; ! stack.full(); ++i) // 放入
stack.push(i);
// stack.push(0); // 测试用,满了继续放入一个数据;
while(! stack.empty()) // 弹出
cout << stack.pop() << endl;
// stack.pop(); // 测试用,弹完了继续弹;
}
catch(exception& ex) {
cout << ex.what() << endl;
return -1;
}
// 测试进制转换
cout << "输入一个整数:" << flush;
int numb;
cin >> num;
cout << "您想看几进制:" << flush;
int base;
cin >> base;
cout << "结果:";
print(numb, base);
return 0;
}
7. 基于链式表的堆栈:
class Stack {
public:
// 构造过程中初始化为空栈
Stack() : m_top(NULL) {}
// 析构过程中释放剩余节点
~Stack() {
for(Node* next; m_top; m_top = next) {
next = m_top->m_next;
delete m_top;
}
}
// 压入
void push(int data) {
/*
Node* node = new Node;
node->m_data = data;
node->m_next = m_top;
m_top = node;
*/
m_top = new Node(data, m_top);
}
// 弹出
int pop() { // 把节点的数据和指针都分别取出来,然后操作;
if(empty())
throw UnderFlow();
int data = m_top->m_data;
Node* next = m_top->m_next;
delete m_top;
m_top = next;
return data;
}
bool empty() const { // 如果为空返回真 1;
return ! m_top; // m_top 为 0 即为空,取反作为返回值;
}
private:
// 下溢
class UnderFlow : public exception {
const char* what() const throw() {
return "堆栈下溢!";
}
};
// 节点
class Node {
public:
Node(int data = 0, Node* next = NULL) : m_data(data), m_next(next) {}
int m_data; // 数据
Node* m_next; // 下一个节点指针
};
Node* m_top;
};
int main() {
try {
Stack stack;
for(int i = 0; i < 5; ++i) // 放入
stack.push(i);
// stack.push(0); // 测使用,满了继续放入一个数据;
while(! stack.empty()) // 弹出
cout << stack.pop() << endl;
// stack.pop(); // 测使用,弹完了继续弹;
}
catch(exception& ex) {
cout << ex.what() << endl;
return -1;
}
}
*********************************************************
DAY02-MIN
1. 队列
1)基本特征:先进先出;
2)基本操作:压入(push)、弹出(pop);
3)实现要点:a.初始化空间,b.从前端(front)弹出,从后端(rear)压入;c.循环使用;d.判空判满;
2. 基于顺序表的队列
class Queue {
public:
Queue(size_t size = 5) : m_array(new int[size]), m_size(size),
m_rear(0), m_front(0), m_count(0) {}
~Queue() {
if(m_array) {
delete[] m_array;
m_array = NULL;
}
}
void push(int data) {
if(full())
throw OverFlow();
if(m_rear >= m_size) // 如果后端指针超出数组大小,重新置零;(可以用 %m_size 实现)
m_rear = 0;
++m_count; // 计算放入数据个数
m_arrary[m_rear++] = data; // 放入数据,同时指针上移一位;
// m_arrary[(m_rear %= m_size )++]=data;
}
void pop() {
if(empty()) {
throw UnderFlow();
if(m_front >= m-size)
m_front = 0;
--m_count; // 计算弹出数据个数;
return m_array[m_front++];
}
}
bool empty() const {
return ! m_count; // 计数器为 0,则为空;
}
bool full() const {
return m_count == m_size; // 压入于弹出数据之差为空间大小,则满;
}
private:
class OverFlow : public exception {
const char* what() const throw() {
return "队列上溢";
}
};
class UnderFlow : public exception {
const char* what() const throw() {
return "队列下溢";
}
};
int* m_array; // 数组
size_t m_size; // 容量
size_t m_rear; // 后端
size_t m_front; // 前端
size_t m_count; // 计数器,判空判满用
};
int main() {
try {
Queue queue;
queue.push (5);
queue.push (10);
cout << queue.pop(); // 输出:5
}
catch(exception& ex) {
cout << ex.what() << endl;
return -1;
}
return 0;
}
3. 基于链式表的队列
class Queue {
public:
Queue() : m_rear(NULL), m_front(NULL) {}
~Queue() {
for(Node* next; m_front; m_front = next) {
next = m_front->m_next;
delete m_front;
}
}
void push(int data) {
Node* node = new Node(data);
if(m_rear)
m_rear->m_next = node;
else
m_front = node;
m_rear = node;
}
int pop() {
if(empty())
throw UnderFlow();
int data = m_front->m_data;
Node* next = m_front->m_next;
delete m_front;
if(! (m_front = next))
m_rear = NULL;
return data;
}
bool empty() const {
return ! m_front && ! m_rear;
}
private:
class UnderFlow : public exception {
const char* what() const throw() {
return "队列下溢";
}
};
class Node {
public:
Node(int data = 0, Node* next = NULL) : m_data(data), m_next(next) {}
int m_data; // 数据
Node* m_next; // 后指针
};
Node* m_rear; // 后端
Node* m_front; // 前端
};
int main() {
try {
Queue queue;
for(int i = 0; i < 20; ++i)
queue.push(i);
while(! queue.empty())
cout << queue.pop() << endl;
}
catch(exception& ex) {
cout << ex.what() << endl;
return -1;
}
return 0;
}
4. 用堆栈的先进后出效果实现队列的先进先出效果:
把上面链式堆栈改作头文件 lstack.h,内容如下:
#ifndef LSTACK_H
#define LSTACK_H
#include <stdexcept>
class Stack {
public:
Stack() : m_top(NULL) {}
~Stack() {
for(Node* next; m_top; m_top = next) {
next = m_top->m_next;
delete m_top;
}
}
// 压入
void push(int data) {
m_top = new Node(data, m_top);
}
// 弹出
int pop() {
if(empty())
throw UnderFlow();
int data = m_top->m_data;
Node* next = m_top->m_next;
delete m_top;
m_top = next;
return data;
}
bool empty() const {
return ! m_top;
}
private:
// 下溢
class UnderFlow : public exception {
const char* what() const throw() {
return "堆栈下溢!";
}
};
// 节点
class Node {
pulic:
Node(int data = 0, Node* next = NULL) : m_data(data), m_next(next) {}
int m_data; // 数据
Node* m_next; // 下一个节点指针
};
Node* m_top;
};
#endif
下面写 queue.cpp 文件:
#include "lstack.h"
#include <iostream>
#include <stdexcept>
using namespace std;
class Queue {
public:
void push(int data) {
m_i.push(data);
}
int pop() {
if(m_o.empty()) {
if(m_i.empty())
throw underflow_error("队列下溢"); // 标准库函数
while(! m_i.empty())
m_o.push(m_i.pop());
}
return m_o.pop();
}
boll empty() const {
return m_i.empty() && m_o.empty();
}
private:
Stack m_i; // 输入栈
Stack m_o; // 输出栈
};
int main() {
try {
Queue queue;
for(int i = 0; i < 20; ++i)
queue.push(i);
while(! queue.empty())
cout << queue.pop() << endl;
}
catch(exception& ex) {
cout << ex.what() << endl;
return -1;
}
return 0;
}
5. 链表
1)基本特征:内存中不连续的节点序列,彼此之间通过指针关联;
前指针(prev)指向前节点,后指针(next)指向后节点;
2)基本操作:追加、插入、删除、遍历;
3)实现要点:略;
#include <stdexcept>
class List {
public:
List() : m_head(NULL),m_tail(NULL) {}
~List() {
for(Node* next; m_head; m_head = next) {
next = m_head->m_next;
delete m_head;
}
}
// 追加(在最后插入)
void append(int data) {
m_tail = new Node(data, m_tail);
if(m_tail->m_prev)
m_tail->m_prev->m_next = m_tail;
else
m_head = m_tail; // 相当于初始化 m_head;
}
// 前插入
void insert(size_t pos, int data) {
for(Node* find = m_head; find; find = find->next)
if(pos-- == 0) {
Node* node = new Node(data, find->m_prev, find);
if(node->m_prev)
node->m_prev->m_next = node;
else // pos 直接为 0 的情况;
m_head = node;
find->m_prev = node;
// node->m_next->m_prev = node;
return;
}
// pos 越界
throw out_of_range("链表越界"); // 标准库
}
// 删除
void erase(size_t pos) {
for(Node* find = m_head; find; find = find->next)
if(pos-- == 0) {
if(find->m_prev) // 有前节点;也就是说 pos 不指向第一个;
find->m_prev->m_next = find->m_next;
else
m_head = find->m_next;
if(find->m_next) // 有后节点;也就是说 pos 不指向最后一个;
find->m_next->m_prev = find->m_prev;
else
m_tail = find->m_prev;
delete find;
return;
}
throw out_of_range("链表越界");
}
// 正向遍历
void forward() const {
for(Node* node = m_head; node; node = node->next)
cout << node->m_data << ' ';
cout << endl;
}
// 反向遍历
void backward() const {
for(Node* node = m_tail; node; node = node->prev)
cout << node->m_data << ' ';
cout << endl;
}
// 下标访问
int& operator[] (size_t index) {
for(Node* find = m_head; find; find = find->m_next)
if(index-- == 0) {
return find->m_data;
}
throw out_of_range("链表越界");
}
const int& operator[] (size_t index) const {
return const_cast<list&>(*this)[index];
}
private:
class Node {
public:
Node(int data = 0, Node* prev = NULL,
Node* next = NULL) : m_data(data),
m_prev(prev), m_next(next) {}
int m_data;
Node* m_prev;
Node* m_next;
};
Node* m_head; // 头指针(链表)
Node* m_tail; // 尾指针(链表)
};
int main() {
try {
List list;
list.append(10);
list.append(10);
list.append(10);
list.insert(1, 15);
list.forward();
list.backward();
list.erase(1);
for(size_t i= 0; i < 3; ++i)
++list[i]; // 每个元素 +1
const List& cr = list;
for(size_t i= 0; i < 3; ++i)
cout << cr[i] << ' '; // 上有 const,则返回右值,如果写成 ++cr[i] 报错;
}
catch(exception& ex) {
cout << ex.what() << endl;
return -1;
}
return 0;
}
6. 练习:实现一个单向线性链表
要求:
1)建立 append()、测长 size()、正反向打印 print()/rprint();
2)逆转头尾列表指针 reverse();
3)获取中间节点值(如果是偶数个,获取靠左那个),只允许遍历一次 middle();
思路:定义两个变量,mid 一次跳一个,node 一次跳两个;node 到头,mid 刚好到中间;
4)有序链表的插入和删除(根据值删除,如果有 3 个 5,则都删除)
class List {
public:
List() : m_head(NULL),m_tail(NULL) {}
~List() {
for(Node* next; m_head; m_head = next) {
next = m_head->m_next;
delete m_head;
}
}
void append(int data) {
Node* node = new Node(data);
if(m_tail)
m_tail->m_next = node;
else
m_head = node;
m_tail = node;
}
size_t size() const {
size_t cn = 0;
for(Node* node = m_head; node; node = node->m_next)
++cn;
return cn;
}
void print() const {
for(Node* node = m_head; node; node = node->m_next)
cout << node->m_data << ' ';
}
// 构造两个堆栈,数据反反得正,或者构造递归
void rprint() const {
rprint(m_head);
cout << endl;
}
/*
void reverse() {
reverse(m_head);
swap(m_head, m_tail);
}
*/
void reverse () {
if(m_head != m_tail) {
Node* p1 = m_tail = m_head;
Node* p2 = p1->m_next;
Node* p3 = p2->m_next;
for(p1->m_next = NULL; p3; p3 = p3->m_next) {
p2->m_next = p1;
p1 = p2;
p2 = p3;
}
(m_head = p2)->m_next = p1;
}
}
int middle() const {
if(! m_head || ! m_tail)
throw underflow_error("链表下溢");
if(m_head == m_tail)
return m_head->m_data;
Node* mid = m_head;
for(Node* node = m_head; node->m_next && node->m_next->m_next; node = m_next->m_next)
mid = mid->m_next;
return mid->m_data;
}
// 插入data,继续保证有序性
void insert(int data) {
}
// 删除所有 data
void remove(int data) {
}
private:
class Node {
public:
Node(int data = 0, Node* next = NULL) : m_data(data), m_next(next) {}
int m_data;
Node* m_next;
};
// 递归
void rprint(Node* head) const {
if(head) {
rprint(head->m_next);
cout << head->m_data << ' ';
}
}
/*
void reverse(Node* head) {
if(head && head->m_next) {
reverse(head->m_next);
head->m_next->m_next = head;
head->m_next = NULL;
}
}
*/
Node* m_head; // 头指针(链表)
Node* m_tail; // 尾指针(链表)
};
int main() {
try {
for(int i = 0; i < 9; ++i)
list.append(i);
cout << list.size() << endl;
}
catch(exception& ex) {
cout << ex.what() << endl;
return -1;
}
return 0;
}
7. 二叉树
1)基本特征:树型结构的最简模型,每个节点最多有两个子节点;
2)单根性:除了根节点外,每个节点有且仅有一个父节点,整棵树只有一个根节点;
3)递归结构:用递归处理二叉树问题可以简化算法;
4)基本操作:生成、遍历
L-D-R:中序遍历
D-L-R:前序遍历
L-R-D:后序遍历
8. 有序二叉树(二叉搜索数)
L <= D <= R (大于节点,放右边;小于节点,放左变)
50 70 20 60 40 30 10 90 80
把上面的九个数字构造成有序二叉树:
50
/ \
20 70
/ \ / \
10 40 60 90
/ /
30 80
思路:50 为根,70 > 50,放右边;20 > 50,放左边;
60 > 50,右边;60 < 70,左边;40 < 50,左边;40 > 20,右边;以此类推;
class Tree {
public:
Tree() : m_root(NULL), m_size(0) {}
// 销毁剩余节点
~Tree() {
clear();
}
// 插入数据
void insert(int data) {
insert(new Node(data), m_root); //
++m_size;
}
// 删除第一个匹配数据,不存在返回 false
void erase(int data) {
Node*& node = find(data, m_root);
if(node) {
// 首先左子树插入右子树
insert(node->left, node->m_right);
// 右子树提升到节点处
Node* temp = node;
node = node->m_right;
delete temp;
--m_size;
return true;
}
return false;
}
// 删除所有匹配数据
void remove(int data) {
while(erase(data));
}
// 清空树
void clear() {
clear(m_root);
m_size = 0;
}
// 将所有 _old 替换为 _new
void updata(int _old, int _new) {
while(erase(_old)) //
insert(_new);
}
// 判断 data 是否存在
bool find(int data) {
return find(data, m_root) != NULL;
}
// 中序遍历
void travel() {
travel(m_root);
}
// 获取树大小
size_t size() {
return m_size;
}
// 获取树高度
size_t height() {
return height(m_size);
}
private:
// 节点
class Node {
public:
Node(int data) : m_data(data), m_left(NULL), m_right(NULL) {}
int m_data; // 数据
Node* m_left; // 指向左子节点或左子树
Node* m_right;
};
void insert(Node* node, Node*& tree) { // 节点和子树
if(! tree)
tree = node; // 建立第一个节点或递归终止
else if(node) {
if(node->m_data < tree->m_data)
insert(node, tree->m_left);
else
insert(node, tree->m_right);
}
}
// 返回子树 tree 中值于 data 相匹配的节点的父节点中指向该节点的成员变量的别名
Node*& find(int data, Node*& tree) {
if(! tree)
return tree; // tree = NULL;
else if(data == tree->m_data)
return tree; // 匹配
else if(data < tree->m_data) // 左子树中找
return find(data, tree->m_left);
else
return find(data, tree->m_right);
}
void clear(Node*& tree) {
if(tree) {
clear(tree->m_left);
clear(tree->m_right);
delete tree;
tree = NULL;
}
}
// 遍历子树
void travel(Node* tree) {
if(tree) {
travel(tree->m_left);
cout << tree->m_data << ' ';
travel(tree->m_right);
}
}
size_t height(Note* tree) {
if(tree) {
size_t lh = height(tree->m_left);
size_t rh = height(tree->m_right);
return (lh > rh ? lh : rh) +1;
}
return 0;
}
Node* m_root; // 树根
size_t m_size; // 树大小
};
int main() {
Tree tree;
tree.insert(50);
tree.insert(70);
tree.insert(20);
tree.insert(90);
tree.travel();
cout << tree.size() << tree.height();
tree.earse(70);
tree.update(20, 80);
tree.find(20);
tree.clear();
return 0;
}
************************************************************
DAY03-MIN
1. 程序设计 = 数据结构 + 算法 + 设计方法学
数值算法(科学/工程计算):微积分、方程组、有限元分析、神经网络等;
非数值算法(系统开发/编程):查找、排序、决策、调度;
2. 冒泡排序
步骤:
1)比较相邻的元素,如果第一个比第二个大,就交换他们;
2)对每一对相邻的元素做同样比较,使得最后元素为最大值,需要 n-1 次比较;
3)对所有元素重复相同的工作,除了最后一个;
这样参加比较的元素会越来越少,直到全部比较完,那么需要 n-1-i 次;
4)极端情况:如果数组是下面这种情况
1 2 3 4 5 7 6
那么,这种情况下我们实际只需要比较 1 次就可以了,不需要 n-1 次,
所以下面的例子里加入了 bool 判断;
void bubbleSort(int data[], size_t size) {
for(size_t i = 0; i < size -1; ++i) {
bool ordered = true; // 用于判断这种情况:1 1 1 2 3 4 5 7 6
for(size_t j = 0; j < size - 1 - i; ++j)
if(data[j+1] < data[j]) {
int temp = data[j+1];
data[j+1] = data[j];
data[j] = temp;
ordered = false;
}
if(ordered)
break;
}
}
int main() {
int data[] = {3, 2, 23, 4, 65, 12, 6};
size_t size = sizeof(data) / sizeof(data[0]);
bubbleSort(data, size);
for(size_t i = 0; i < size; ++i)
cout << data[i] << ' ';
cout << endl;
}
冒泡排序特点:
本例题的平均时间复杂度:O(n^2); 随着样本增加,时间复杂度会急剧上升;(抛物线)
稳定性:排序过程中,如果两个数据相同,则不改变位置,称为稳定排序;
敏感性:对数据的有序性非常敏感,就是对数据原有的顺序很在意;
4. 插入排序
步骤:
1)从第二个开始,和前面的每一个相比;
2)如果比前一个大/相等,则排在前一个后面;
3)如果比前一个小,则把前一个数后移一位;继续比较,以此类推;
void insertSort(int data[], size_t size) {
for(size_t i = 1; i < size; ++i) {
int temp = data[i];
size_t j;
for(j = i; j > 0 && temp < data[j-1]; --j) // 比前一个小,则后遗;仅仅确定插入位置
data[j] = data[j-1];
if(j != i) // 相等的情况
data[j] = temp; // 执行插入动作;
}
}
int main() {
int data[] = {3, 2, 23, 4, 65, 12, 6};
size_t size = sizeof(data) / sizeof(data[0]);
insertSort(data, size);
for(size_t i = 0; i < size; ++i)
cout << data[i] << ' ';
cout << endl;
}
插入排序特点:
本例题的平均时间复杂度:O(n^2); 和冒泡一样;
冒泡排序内层 for 循环需要三次赋值,插入排序只是一次赋值;速度略优于冒泡;
稳定性:稳定,和冒泡一样;
敏感性:敏感,和冒泡一样;
5. 选择排序
步骤:
1)选出最小的,和第一个作交换;
2)剩下的选出最小的,和第二个作交换;
void selectSort(int data[], size_t size) {
for(size_t i = 0; i < size -1; ++i) {
size_t min = i;
for(size_t j = i; j < size; ++j)
if(data[j] < data[min]
min = j;
if(min != i) {
int temp = data[i];
data[i] = data[min];
data[min] = temp;
}
}
}
int main() {
int data[] = {3, 2, 23, 4, 65, 12, 6};
size_t size = sizeof(data) / sizeof(data[0]);
selectSort(data, size);
for(size_t i = 0; i < size; ++i)
cout << data[i] << ' ';
cout << endl;
}
选择排序特点:
本例题的平均时间复杂度:O(n^2); 和冒泡一样;
循环次数和插入排序一样;
稳定性:稳定,和冒泡一样;
敏感性:不敏感,无论数据是否有序,都得寻找最小值;
6. 快速排序(分组排序)
步骤:
1)任选一个数(一般选择中间位置)作为基准;
2)重排序列,比基准小的放左边,比基准大的放右边,和基准相等的任意,这个过程叫做分组;
3)以递归的方式,分别对小于基准的分组和大于基准的分组分别进行排序;
void quickSort(int data[], size_t left, size_ right) {
size_t p = (left + right) / 2; // 找基准值
int pivot = data[p]; // 备份基准值
for(size_t i = left, j = right; i < j;) { // 只要 i 和 j 不相碰,循环继续;
while(! (i >= p || pivot < data[i]))
++i;
if(i < p) { // 第二种情况
data[p] = data[i];
p = i;
}
while(! (j <= p || data[j] < pivot))
++j;
if(j > p) {
data[p] = data[j];
p = j;
}
}
data[p] = pivot;
if(p - left > 1)
quickSort(data, left, p - 1);
if(right - p > 1)
quickSort(data, p + 1, right);
}
int main() {
int data[] = {3, 2, 23, 4, 65, 12, 6};
size_t size = sizeof(data) / sizeof(data[0]);
quickSort(data, 0, size -1);
for(size_t i = 0; i < size; ++i)
cout << data[i] << ' ';
cout << endl;
}
快速排序特点:
平均时间复杂度为:O(NlogN), 也是抛物线,但是增长缓慢;
稳定性:不稳定;
如果每次分组都是均匀分组,那么速度最快;
7. 自己写 my_qsort() 函数,实现 qsort() 排序功能:
#include <cstring>
#include <cstdlib>
static void quickSort(void* base, size_t left, size_t right, size_t size,
int (*compar)(const void*, const void*)) {
size_t p = (left + right) / 2;
void* pivot = malloc(size);
memcpy(pivot, base + p * size, size);
size_t i,j;
for(i = left, j = right; i < j; ) {
while(!(i >= p || cmpar(pivot, base + i * size) < 0))
++i;
if(i < p) {
memcpy(base + p * size, base + i * size, size);
p = i;
}
while(!(j <= p || cmpar(base + j * size, pivot) < 0))
--j;
if(j > p) {
memcpy(base + p * size, base + j * size, size);
p = j;
}
}
memcpy(base + p * size, pivot, size);
free(pivot);
if(p - left > 1)
quickSort(base, left, p-1, size, compar);
if(right - p > 1)
quickSort(base, p + 1, right, size, compar);
}
void my_qsort(void* base, size_t numb, size_t size,
int (*compar)(const void*, const void*)) {
quickSort(base, 0, numb - 1, size, compar); // 调用快速排序
}
// 常量排序
int cmpInt(const void* a, const void* b) {
return *(const int*)a - *(const int*)b;
}
// 字符串排序
int cmpStr(const void* a, const void* b) {
return strcmp(*(const char* const*)a, *(const char* const*)b);
}
// 把 data1,data2 合并到 data3,并保证有序;前提:data1 和 data2 都是有序的;
// 思路:1 和 2 里的数据比大小,把小的放入 3,以此类推;
void merge(int data1[], size_t size1, int data2[], size_t size2, int data3[]) {
size_t i = 0, j = 0, k = 0;
for(;;) // 死循环;
if(i < size1 && j < size2) // 如果 data1 和 data2 里面都有数据;
if(data1[i] <= data2[j]) // 如果 data1 小,放入 data3;
data3[k++] = data1[i++];
else
data3[k++] = data2[j++];
else if(i < size1) // 如果 data2 数据完了;
data3[k++] = data1[i++]; // 把 data1 里剩余的数据直接赋值给 data3;
else if(j < size2)
data3[k++] = data2[j++];
else
break;
}
// l 到 m 之间有序,m 到 r 之间有序,合并后依然有序;
void merge(int data[], size_t l, size_t m, size_t r) {
int* res = new int[r-l+1];
merge(data+l, m-l+1, data+m+1, r-m, res);
for(size_t i = 0; i < r - l + 1; ++i)
data[l+i] = res[i];
delete[] res;
}
// 归并排序
void mergeSort(int data[], size_t left, size_t right) {
if(left < right) {
int min = (left + right) / 2;
mergeSort(data, left, mid);
mergeSort(data, mid + 1, right);
merge(data, left, mid, right);
}
}
int main() {
int* pa[] = {2, 3, 33, 6, 12};
my_qsort(pa, 5, sizeof(pa[0]), cmpInt);
char* ps[] = {"shanghai", "beijing", "xian"};
my_qsort(ps, 3, sizeof(ps[0]), cmpStr);
int data1[] = {22,33,45, 78}; // data1 是有序的;
int data2[] = {23,55}; // data2 也是有序的;
int data3[6];
merge(data1, 4, data2, 2, data3); // 把 data1,data2 合并到 data3,并保证有序;
for(size_t i = 0; i < 6; ++i)
cout << data3[i] << ' ';
cout << endl;
int data[] = {10, 6, 7, 8, 5, 6, 7, 2, 1};
merge(data, 1, 3, 6); // 1~3 有序,4~6 有序;
for(size_t i = 0; i < 9; ++i)
cout << data[i] << ' ';
cout << endl;
return 0;
}
8. 归并排序
步骤:
1)申请空间,其大小为两个有序序列之和;
2)设定两个指针,分别指向两个有序序列的起始位置;
3)比较两个指针的目标,选这小值放入合并空间,并将指针移到下个位置;
4)重复步骤 3,直到某一指针到达序列尾;
5)将另一序列的剩余元素复制到合并空间;
例题:把 data1[],data2[] 合并到 data3[],并保证有序;前提:data1 和 data2 都是有序的;
思路:1 和 2 里的数据比大小,把小的放入 3,以此类推;
// 异地排序
void merge(int data1[], size_t size1, int data2[], size_t size2, int data3[]) {
size_t i = 0, j = 0, k = 0;
for(;;) // 死循环;
if(i < size1 && j < size2) // 如果 data1 和 data2 里面都有数据;
if(data1[i] <= data2[j]) // 如果 data1 小,放入 data3;
data3[k++] = data1[i++];
else
data3[k++] = data2[j++];
else if(i < size1) // 如果 data2 数据完了;
data3[k++] = data1[i++]; // 把 data1 里剩余的数据直接赋值给 data3;
else if(j < size2)
data3[k++] = data2[j++];
else
break;
}
// 本地排序
// l 到 m 之间有序,m 到 r 之间有序,合并后依然有序;
void merge(int data[], size_t l, size_t m, size_t r) {
int* res = new int[r-l+1];
merge(data+l, m-l+1, data+m+1, r-m, res);
for(size_t i = 0; i < r - l + 1; ++i)
data[l+i] = res[i];
delete[] res;
}
// 归并排序
void mergeSort(int data[], size_t left, size_t right) {
if(left < right) {
int min = (left + right) / 2;
mergeSort(data, left, mid);
mergeSort(data, mid + 1, right);
merge(data, left, mid, right);
}
}
int main() {
int data1[] = {22,33,45, 78}; // data1 是有序的;
int data2[] = {23,55}; // data2 也是有序的;
int data3[6];
merge(data1, 4, data2, 2, data3); // 把 data1,data2 合并到 data3,并保证有序;
for(size_t i = 0; i < 6; ++i)
cout << data3[i] << ' ';
cout << endl;
int data[] = {10, 6, 7, 8, 5, 6, 7, 2, 1};
merge(data, 1, 3, 6); // 1~3 有序,4~6 有序;
for(size_t i = 0; i < 9; ++i)
cout << data[i] << ' ';
cout << endl;
return 0;
}
递归排序特点:
平均时间复杂度:O(2NlogN);
稳定性:稳定;
数据敏感性:不敏感;
非就地排序,需要分配空间,不适合对大数据量排序;
9. 线性查找
从表头开始,依次比较,直到找到与查找目标匹配的元素;或者找不到;
int lineFind(int data[], int size, int key) {
for(int i = 0; i < size; ++i)
if(data[i] == key)
return i;
return -1;
}
int main() {
int data[] = {3, 5, 5, 2, 8};
size_t size = sizeof(data) / sizeof(data[0]);
size_t i = lineFind(data, size, 2);
if(i == -1)
cout << "can't find" << endl;
else
cout << data[i] << endl;
return 0;
}
线性查找特点:
平均时间复杂度:O(N);
数据敏感性:对数据有序性无任何要求;
10. 二分查找
前提,序列需要是有序的;
将表中间位置的值与查找目标比较,
如果比中值小,放左边;反之放右边;如果相等,则查找成功;
int binFind(int data[], int size, int key) {
int left = 0;
int right = size -1;
while(left <= right) {
int mid = (left + right) /2;
if(data[mid] < key) // 如果 key 大于中间值,到右边查找;
left = mid + 1;
else if(key > data[min]) // 如果 key 小于中间值,到左边查找;
right = mid - 1;
else
return mid;
}
return -1; // 查找失败;
}
int main() {
int data[] = {3, 5, 5, 6, 8};
size_t size = sizeof(data) / sizeof(data[0]);
size_t i = binFind(data, size, 6);
if(i == -1)
cout << "can't find" << endl;
else
cout << data[i] << endl;
return 0;
}
二分查找特点:
平均时间复杂度:O(logN); 快!