插入排序
//插入排序,平均情况O(n^2),最好O(n),最坏O(n^2),,空间复杂度O(1),稳定
/*
插入排序思想
a[5] = {2,1,3,4,5}
外层循环:a[0]不用管,默认有序,从a[1]开始往后循环
内层循环:将需要移动的元素a[1]存入临时变量
遍历a[1]前面的元素,与当前a[1]进行比较,若遍历的当前元素大于a[1],元素后移a[j] = a[j - 1]
直到数据元素小于等于临时变量
移动完后,将当前位置赋值为临时变量
*/
#include <iostream>
using namespace std;
//频繁交换
void insertSortBad(int *a, int n){
int i, j;
for (i = 1; i < n; ++i){
for (j = i; a[j - 1] > a[j] && j > 0; --j){
swap(a[j - 1], a[j]);
}
}
}
//将频繁交换转换为赋值
void insertSort(int *a, int n){
//临时变量,存放需要移动的元素
int temp = 0;
int i, j;
for (i = 1; i < n; ++i){
temp = a[i];
for (j = i; a[j - 1] > temp && j > 0; --j){
a[j] = a[j - 1];
}
a[j] = temp;
}
}
int main(){
int a[7] = { 2, 1, 3, 4, 5, 9, 8 };
insertSortBad(a, 7);
for (int i = 0; i < 7; ++i)
cout << a[i] << endl;
system("pause");
return 0;
}
二分查找
/**************************************************************************
** 二分查找法
** 在有序数组中查找target,查找到返回index,否则返回-1
*************************************************************************/
#include <iostream>
using namespace std;
int binarySearch(int arr[], int n, int target){
//在[l,r]中查找target
//包含右边界
int l = 0, r = n - 1;
int res;
while (l <= r){
//int mid = (l + r) / 2;
//防止溢出
int mid = l + (r - l) / 2;
if (arr[mid] == target){
res = mid;
break;
}
//在[l,mid-1]中查找target
else if (arr[mid] > target)
r = mid - 1;
//在[mid+1,r]中查找target
else
l = mid + 1;
}
return res;
}
int main(){
int a[6] = { 0, 1, 2, 3, 4, 5 };
int res = binarySearch(a, 6, 4);
cout << res << endl;
system("pause");
return 0;
}
快速排序
//快速排序思想:
//选出一个哨兵,通过一趟排序将待排序列分割成两部分
//其中一部分小于哨兵值,另外一部分大于哨兵值
//然后再对两部分分别进行上述操作,直到排序完成
//解释时间复杂度为nlogn
//分治算法,每次选出哨兵,将待排序列分成两部分,一直分下去,直到只有一个元素,平均分开,相当于n个结点的二叉树,共有logn层,递归深度为logn
//每一层都需要对当前分组的元素排序,问题规模大概为n,第一层遍历n个元素,第二层遍历n-1个...直到最后一层为一个
//nlogn
//解释空间复杂度O(logn)~O(n)
//主要是递归造成的栈空间的使用
//最好情况递归树深度为logn,最坏情况为n
//快排的最差情况什么时候发生?
//1. 已排序
//2. 数值全部相等(1的特殊情况)
//在上面的情况下选择的标定元素一直为第一个,则时间复杂度变为O(n^2)
//快速排序,平均情况O(nlogn),最好O(nlogn),最坏O(n^2)(选择标定元素有关),空间复杂度O(logn)~O(n),不稳定
/**************************************************************************
** 要取得[a,b)的随机整数,使用(rand() % (b-a))+ a;
** 要取得[a,b]的随机整数,使用(rand() % (b-a+1))+ a;
** 要取得(a,b]的随机整数,使用(rand() % (b-a))+ a + 1;
** 通用公式:a + rand() % n;其中的a是起始值,n是整数的范围。
** 要取得a到b之间的随机整数,另一种表示:a + (int)b * rand() / (RAND_MAX + 1)。
** 要取得0~1之间的浮点数,可以使用rand() / double(RAND_MAX)。
*************************************************************************/
#include <iostream>
using namespace std;
//双路快排,尽量写这种
//begin,begin+1...i...j...end
int partition2(int *data, int start, int end){
//产生start和end之间的随机数
int index = (rand() % (end - start + 1)) + start;
//将选中的数字交换到start位置
swap(data[index], data[start]);
int pivot = data[start];
//选择的pivot为start位置
//data[start+1, i) <= pivot i-1 为小于v的最后一个元素,i为当前左边访问的元素
//data(j, end] >= pivot j+1 为大于v的第一个元素,j为当前右边访问的元素
int i = start + 1, j = end;
while (true){
while (i <= end && data[i] <= pivot)
i++;
while (j >= start + 1 && data[j] >= pivot)
j--;
if (i > j)
break;
swap(data[i], data[j]);
//下面这两行,swap交换之后,双方需要移动,否则会增加一次无用的比较
i++;
j--;
}
//最后j停止在<= v的最后一个位置, i停止在>=pivot的第一个位置,pivot与j进行交换
swap(data[start], data[j]);
return j;
}
void swapOffer(int &a, int &b){
int temp = a;
a = b;
b = temp;
}
//单路随机快排
//begin,begin+1...small,small+1...end
int partition(int *data, int start, int end){
//产生start和end之间的随机数
int index = (rand() % (end - start + 1)) + start;
//将选中的数字交换到start位置
swap(data[index], data[start]);
//选择的pivot为start位置
int pivot = data[start];
//我们要达到这样的效果
//data[start+1, small] < v small为小于pivot的最后一个元素
//data[small+1, i - 1] >= v small+1为大于等于pivot的第一个元素
//注意small的取值,small是为了标识小于
int small = start;
for (int i = start + 1; i <= end; ++i){
if (data[i] < pivot){
//若当前的元素小于pivot,需要将该元素放到data[start+1, small]中紧挨着small位置
//将small+1和i进行交换,并将small的长度加长
swap(data[small + 1], data[i]);
++small;
}
}
//最后将start放在应该的位置,即small和small+1之间,因为左侧全是小于pivot的,因此将small和pivot交换
swap(data[small], data[start]);
return small;
}
void quickSortOffer(int *data,int start, int end){
if (start == end)
return;
if (start < end){
int index = partition2(data, start, end);
quickSortOffer(data, start, index - 1);
quickSortOffer(data, index + 1, end);
}
}
int main(){
int arr[8] = { 2, 1, 3, 78, 78,53, 13, 20 };
quickSortOffer(arr, 0, 7); //0表示从数组0位置开始,到4位置排序
int i;
for (i = 0; i<8; ++i){
printf("%d ", arr[i]);
}
system("pause");
return 0;
}
归并排序
//两个序列从相同位置往右遍历比较,根据大小重新排列在新数组中的前提是,左右两个序列都是从小到大排列的
//归并排序思想:先递归再归并
//先递归,将左右归并为有序序列,再将左右进行归并
//将左右有序归并时,两个序列从相同位置往右遍历比较,根据大小重新排列在新数组中
//核心函数是merge,Msort函数是递归分治调用merge的过程
//归并排序,平均情况O(nlogn),最好O(nlogn),最坏O(nlogn),空间复杂度O(n + logn),稳定
//解释时间复杂度为nlogn
//一趟归并要将整个序列所有元素遍历一遍,复杂度为O(n)
//递归树的高度为logn,每次递归都要一趟归并
//nlogn
//解释空间复杂度O(n + logn)
//主要是递归造成的栈空间的使用logn,和每次归并都需要将原数组归并到新数组,再拷贝回原数组,和每次归并都需要将原数组归并到新数组,再拷贝回原数组O(n)
//O(n + logn)
#include <iostream>
#include <vector>
using namespace std;
//函数是将sr两侧归并的数据,重新排列进tr中
//sr[s...m, m+1...t]
//int num = 0;
void merge(int sr[], int s, int m, int t){
int length = t - s + 1;
int *tr = new int[length];
int i = s, j = m + 1, k = 0;
while(i <= m && j <= t){
if (sr[i] < sr[j])
tr[k++] = sr[i++];
else{
//num += m - i + 1;
tr[k++] = sr[j++];
}
}
//当两侧数据长度并非完全相等时,只需要将左、右侧两侧剩余数据拷贝即可
while (i <= m)
tr[k++] = sr[i++];
while (j <= t)
tr[k++] = sr[j++];
//将辅助数组的元素拷贝到原数组中
for (k = 0; k < length; k++)
sr[s + k] = tr[k];
}
//sr[s, t]原数组
void msort(int sr[], int s, int t){
if (s >= t)
return;
//创建平分中点
int m = s + (t - s) / 2;
msort(sr, s, m);
msort(sr, m+1, t);
//优化,因为左右两侧都是排序好的数组,只有当左侧最大大于右侧最小时才合并,因为合并意味着比较,会浪费时间
if (sr[m] > sr[m+1])
merge(sr, s, m, t);
}
int main(){
int sr[10] = { 2, 1, 3, 78, 87, 53, 13, 20, 0, 10};
msort(sr, 0, 9);
//std::cout << num << endl;
for (int i = 0; i < 10; i++){
std::cout << sr[i] << endl;
}
std::system("pause");
return 0;
}
堆排序
//堆排序思想:将待排序列先构造一个最大堆,堆顶元素即为最大值,然后移走堆顶元素,调整剩余元素为最大堆,如此反复执行,便得到有序序列
//若从小到大排列,建立最大堆
//若从大到小排列,建立最小堆
//堆排序,平均情况O(nlogn),最好O(nlogn),最坏O(nlogn),空间复杂度O(1),不稳定
//解释时间复杂度O(nlogn)
//建立堆的过程是nlogn,遍历所有非叶子结点,将其和子树调整为最大堆
//调整堆的过程是logn,整体是nlogn
//由数组变为最大堆有两种方式,一种是逐个插入(O(n)),一种是heapify(从第一个非叶子结点开始调整,bottom to up)(O(1))
//大话数据结构书上的排序基本都是数组的0位置不放元素,从1开始
//这里从0位置开始
#include <iostream>
#include <vector>
using namespace std;
void build_max_heap(vector<int> &a, int len);
void heapadjust(vector<int> &a, int i, int len);
void heap_sort(vector<int> &a) {
int len = a.size();
build_max_heap(a, len);
for (int i = len - 1; i >= 1; i--) {
//将堆顶的元素和最后一个元素交换位置
swap(a[0], a[i]);
//每次将数组前面的部分再调整为最大堆,后续调整的时候传进去的就是当前数组能取到的最大下标了,因为i赋初值时已经解决
heapadjust(a, 0, i - 1);
}
}
//建堆的意思是将数组转换成最大堆
//本质:从下往上,从右往左,将每个非叶子结点当做根节点,将其和子树调整成最大堆,首先找到最后一个叶子结点的父节点len / 2 - 1
//最大堆,每个结点都大于等于左右孩子结点的值
void build_max_heap(vector<int> &a, int len) { //建堆
for (int i = len / 2 - 1; i >= 0; i--) {
heapadjust(a, i, len - 1); //建堆的时候应该传进去len-1,即当前数组能取到的最大下标,即保证堆调整过程中左右孩子不会数组越界
}
}
//以i当做根节点,调整其和其子树为最大堆
void heapadjust(vector<int> &a, int i, int len){
int temp, j;
temp = a[i];
//当前结点为j,左孩子序号是2*i,右孩子序号是2*i+1
//这里的结点从0开始,不是从1开始,因此左孩子序号是2*i + 1,右孩子序号是2*i+2
for (j = 2*i+1; j <= len; j *= 2){
//先比较左右孩子,找到左右孩子中较大的那个
if (j < len && a[j] < a[j+1])
++j;
//如果左右孩子中最大值小于当前值,则不需要移动
if (a[j] <= temp)
break;
//若最大值大于当前值,需要将最大值往前移动,赋给i位置
//此时不要动j,直到找到j所在位置再赋值
a[i] = a[j];
//将当前值和左右孩子比较后,若移动了,则继续往下比较,将j向下移动到刚才较大的孩子处
i = j;
}
//直到找到temp的合适位置
a[i] = temp;
}
int main(){
vector<int>a;
a.push_back(7);
a.push_back(6);
a.push_back(5);
a.push_back(4);
a.push_back(1);
a.push_back(2);
a.push_back(3);
heap_sort(a);
for (int i = 0; i<7; ++i){
printf("%d ", a[i]);
}
system("pause");
return 0;
}
二叉树非递归遍历
//二叉树的前序,中序和后序遍历是根据根结点的位置来判断
//前序:根左右
//中序:左根右
//后序:左右根
//叶子结点也需要判断左右节点,只不过左右节点都是空
//非递归前序遍历,根左右
//(1) 从根结点开始,向左遍历压栈并输出
//(2) 一直找到二叉树最左边的结点,将最左侧的叶子结点压入栈
//(3) 出栈,指向该结点的右孩子
//(4) 将右孩子作为根节点重复(1)(2)(3)
void Pretravel(BiNode* root)
{
if (!root)
{
return;
}
stack<BiNode*> st;
BiNode* p = root;
while (p || !st.empty())
{
while (p){
cout << p->data;
st.push(p);
p = p->lchild;
}
if (!st.empty())
{
p = st.top();
st.pop();
p = p->rchild;
}
}
}
//非递归中序遍历,左根右
//先输出最左侧叶子结点
//(1) 从根结点开始,往左遍历压栈
//(2) 找到最左侧的叶子结点,也将其压栈
//(3) 出栈,输出结点值,并指向该结点的右孩子
//(4) 将右孩子作为根结点继续(1)(2)(3)
void Intravel(BiNode* root){
if (root == NULL)
return;
stack<BiNode*>st;
BiNode *p = root;
while (!st.empty() || p){
while (p){
st.push(p);
p = p->lchild;
}
if (!st.empty()){
p = st.top();
st.pop();
cout << p->data;
p = p->rchild;
}
}
}
//非递归后序遍历,左右根
// 维护一个pre结点
//(1) 从根结点开始,往左遍历压栈
//(2) 找到最左侧的叶子结点,也将其压栈
//(3) 出栈,判断当前的结点是不是叶子结点或是不是根结点(上一次访问的是右孩子)
//(4) 若是,输出结点值,更新pre指针
//(5) 若不是,指向右孩子,重复(1)(2)(3)(4)
void behtravel(BiNode* root){
if (NULL == root)
return;
stack<BiNode *> st;
BiNode * p = root;
BiNode * pre = NULL;
while (!st.empty || p){
while (p){
st.push(p);
p = p->lchild;
}
if (!st.empty()){
p = st.top();
st.pop();
//右孩子为空(左叶子结点和右叶子结点) 或 刚刚访问的是该结点的右孩子(根结点)
if (!p->rchild || pre == p->rchild){
cout << p->data;
pre = p;
}
//右孩子不为空,则将刚刚出栈的结点重新压入,指向结点的右孩子
else{
st.push(p);
p = p->rchild;
}
}
}
}
单例模式
//单例思路:静态成员建议在类外进行初始化,但在类内也可以初始化,只是通过类名访问静态成员的属性时,访问不到
//构造函数声明为private或protect防止被外部函数实例化
//内部保存一个private static的类指针保存唯一的实例
//实例的动作由一个public的类方法代劳
//懒汉模式:在getinstance中实例化
//饿汉模式:在单例类定义时实例化
//**********************
//懒汉模式最初实现
//**********************
//#include <iostream>
//
//using namespace std;
//
//class single{
//private:
// static single *p;
// single(){}
// ~single(){}
//
//public:
// static single* getinstance();
//};
//
//single* single::p = NULL;
//single* single::getinstance(){
// if (NULL == p)
// p = new single;
//
// return p;
//}
//**********************
//懒汉模式线程安全经典实现
//**********************
//#include <iostream>
//#include <unistd.h>
//#include <pthread.h>
//
//using namespace std;
//
//
//class single{
//private:
// static single *p;
// static pthread_mutex_t lock;
// single(){
// pthread_mutex_init(&lock, NULL);
// }
// ~single(){}
//
//public:
// static single* getinstance();
//
//};
//pthread_mutex_t single::lock;
//single* single::p = NULL;
//single* single::getinstance(){
// if (NULL == p){
// pthread_mutex_lock(&lock);
// if (NULL == p)
// p = new single;
// }
// pthread_mutex_unlock(&lock);
// return p;
//}
//**************************************************
//懒汉模式线程安全内部静态变量实现
//将经典实现中的私有唯一实例删掉
//改为在instance函数里定义一个静态的实例
//也可以保证拥有唯一实例,在返回时只需要返回其指针就可以
//**************************************************
//#include <iostream>
//#include <unistd.h>
//#include <pthread.h>
//
//using namespace std;
//
//
//class single{
//private:
// static pthread_mutex_t lock;
// single(){
// pthread_mutex_init(&lock, NULL);
// }
// ~single(){}
//
//public:
// static single* getinstance();
//
//};
//pthread_mutex_t single::lock;
//single* single::getinstance(){
// pthread_mutex_lock(&lock);
// static single obj;
// pthread_mutex_unlock(&lock);
// return &obj;
//}
//************************************************************
//饿汉模式,在定义单例类最初就实例化,此后返回的就一个,感觉相当于全局变量
//在饿汉模式下,在单例类定义的时候就已经定义了一个对象,对类进行了初始化。
//后面不管哪个线程调用成员函数getinstance(),都只不过是返回一个对象的指针而已。
//所以是线程安全的,不需要在成员函数getinstance中加锁。
//************************************************************
#include <iostream>
using namespace std;
class single{
private:
static single* p;
single(){}
~single(){}
public:
static single* getinstance();
};
single* single::p = new single();
single* single::getinstance(){
return p;
}
int main(){
single *p1 = single::getinstance();
single *p2 = single::getinstance();
if (p1 == p2)
cout << "same" << endl;
system("pause");
return 0;
}
智能指针
//智能指针的设计与实现
//智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。
//每次创建类的新对象时,初始化指针并将引用计数置为1;
//当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;
//对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;
//调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
//所有的智能指针都会重载 -> 和 * 操作符。智能指针还有许多其他功能,比较有用的是自动销毁。
#include <iostream>
#include <memory>
template<typename T>
class SmartPointer {
private:
T* _ptr;
//在赋值时,需要修改赋值后指针的引用计数
size_t* _count;
public:
//初始化
SmartPointer(T* ptr = NULL) :
_ptr(ptr) {
if (_ptr) {
_count = new size_t(1);
}
else {
_count = new size_t(0);
}
}
//拷贝构造
SmartPointer(const SmartPointer& ptr) {
if (this != &ptr) {
this->_ptr = ptr._ptr;
this->_count = ptr._count;
(*this->_count)++;
}
}
//重载=运算符
SmartPointer& operator=(const SmartPointer& ptr) {
if (this->_ptr == ptr._ptr) {
return *this;
}
if (this->_ptr) {
(*this->_count)--;
if (this->_count == 0) {
delete this->_ptr;
delete this->_count;
}
}
this->_ptr = ptr._ptr;
this->_count = ptr._count;
(*this->_count)++;
return *this;
}
//重载*
T& operator*() {
assert(this->_ptr == nullptr);
return *(this->_ptr);
}
//重载->
T* operator->() {
assert(this->_ptr == nullptr);
return this->_ptr;
}
~SmartPointer() {
//因为引用计数是指针,当智能指针声明为空时,仍需释放
if (*this->_count == 0) {
delete this->_ptr;
delete this->_count;
std::cout << "释放" << std::endl;
}
else
(*this->_count)--;
if (*this->_count == 0) {
delete this->_ptr;
delete this->_count;
std::cout << "释放" << std::endl;
}
}
size_t use_count(){
return *this->_count;
}
};
int main() {
{
//只初始化了两次
SmartPointer<int> sp(new int(10));
SmartPointer<int> sp2(sp);
SmartPointer<int> sp3(new int(20));
sp2 = sp3;
std::cout << sp.use_count() << std::endl;
std::cout << sp3.use_count() << std::endl;
//SmartPointer<int> sp(NULL);
//std::cout << sp.use_count() << std::endl;
}
system("pause");
return 0;
}
类的默认函数
#include <string.h>
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
class String
{
public:
String()
: _pstr(new char[1]())
{}
String(const char * pstr)
: _pstr(new char[strlen(pstr) + 1]())
{
strcpy(_pstr, pstr);
cout << "String(const char *)" << endl;
}
//
//复制控制函数
String(const String & rhs)
: _pstr(new char[strlen(rhs._pstr) + 1]())
{
strcpy(_pstr, rhs._pstr);
cout << "String(const String &)" << endl;
}
String & operator=(const String & rhs)
{
cout << "String& operator=(const String &)" << endl;
if(this != & rhs) {
delete [] _pstr;
_pstr = new char[strlen(rhs._pstr) + 1]();
strcpy(_pstr, rhs._pstr);
}
return *this;
}
//如果传递右值时,具有移动语义的函数会优先执行
//移动构造函数
String(String && rhs)
: _pstr(rhs._pstr)
{
rhs._pstr = NULL;//将临时对象的指针设为空
cout << "String(String &&)" << endl;
}
//移动赋值运算符函数
String & operator=(String && rhs)
{
cout << "String & operator=(String &&)" << endl;
if(this != & rhs) {
delete [] _pstr;
_pstr = rhs._pstr;
rhs._pstr = NULL;
}
return *this;
}
~String()
{
delete [] _pstr;
cout << "~String()" << endl;
}
friend std::ostream & operator<<(std::ostream & os, const String & rhs);
private:
char * _pstr;
};
String str("hello");
//右值引用本身是左值还是右值,取决于其有没有名字
//右值引用作为函数的返回值, 此时是右值
String && func()
{
return std::move(str);
}
std::ostream & operator<<(std::ostream & os, const String & rhs)
{
os << rhs._pstr;
return os;
}