c++进阶学习(未完待续)

C++

数组与指针

//数组在初始化时数组大小只能写常量
int arr[10] = { 0 };                 //arr有10位,并且全为0
int arr[n] = {0};				    //不合法
//但可以这样
int* arr = new int[n]			   //开辟一个n个int空间的地址,让arr作为指针来接
int* arr = {0,1,2,3};				//不合法,只能接一个0
char* arr = {"hello"};				//不合法,"hello"这时一个常量,不能用指针指向
const char* arr = {"hello"}           //合法,规定指针不改变常量的值
								  //"hello"是一个常量,执行这句是"hello"被分成'h','e','l','l','e'放在例如0x00,0x01,0x02,0x03,0x04这五个地址上,arr作为一个指针指向0x00('h'),编译器会理解arr = "hello"
char arr[] = {"hello"}               //合法,是一个字符数组 
//即使是c风格的字符串(字符数组),在末尾也会加上\0,(int *arr or int arr[])

当数组作为形参时:
  一.引用传递
	1. type fun(int *&arr....)		arr作为左值引用,当函数被调用时值传递为
								  int *&arr = array  (左右类型都是int*,左值是右值的别名)
	2. (int (&arr)[10],.....)       &arr要阔起来因为优先级不够,[]中必须填写一个常量
  二.指针传递
	3.(int arr[], ....)            一维数组传递,虽然数组不允许拷贝和复制,但是在形参中数组退化成为一个指针 
	4. (int * arr .....)		   指针传递,arr指向数组首地址
	
	
	

类型声明规则

右左原则:首先找到变量名,先往右看,当右边没有的话或者出现“)”的时候往左看,当左边没有的话或者出现“(”,循环这个过程,直至读完变量或者函数的声明:

int a;                         //a是一个int类型的变量
int *a;                        //a是一个指针,指向int型
int a();						//a是一个函数,返回值是int
int (*a)();					//a是指向一个返回int型的函数的函数指针
int a[5];					//a是一个数组,有5个空间,每一个空间大小(类型)是int
int *a[5];					//a是一个数组,有5个空间,每一个空间是一个指向int类型的指针(a是一个存储5个指针的数组)
int (*a)[5]; 				//a是一个指针,有5个空间,每一个空间大小是int(a一个是指向一个5个int类型的数组的指针)
int (*a[])();				//a是一个数组,数组装着指针,每个指针指向一个返回值为int型的函数
int (*(*a)[])();			//a是一个指针,指向一个数组,数组里装的都是指针,每个指针指向一个返回类型为int的函数(a是一个指向int型函数指针组成的数组的指针)
int const a;				//a是一个int类型是的常量
const int a;				//int const a 等价与 const int a
int const *a;				//a是一个指向一个int常量的指针,意味着a不是常量,可以给a赋值让a指向其他地方,*a(解引用)才是常量
int *const a; 				//a是一个常量指针,指向一个int类型,意味着指针a不可改变,在初始化后就一直指向这个地址,但是指向的int类型数据(*a)可以改变,


重载运算符

两种写法:

  1. 返回值 operator运算符(左参数,右参数){

    ​ 实现体

    }

  2. 返回值 operator运算符(运算符右边参数的引用){

    ​ 实现体

    }

第二种写法一般写在类里面,因为可以用this来指代左边的参数

显式类型转换

命名的强制类型转换:

name-cast<type>(表达式);
double a;
const char* p;
static_cast<int>(a);				//不支持不想关类型转换,比如一个类转换成一个int等,其他都支持即使是把子类强转为父类,(但是不安全,可能会越界)
const_cast<char*>(p);				//只能去掉const和加上const
dynamic_cast<Base *>(new Son);		//只支持安全的类型转换,即把子类转换成父类,其他的都不支持
reinterpret_cast<type1><a>;			//最不安全的,不支持基本类型转换,其他的都支持

尽量用const代替宏定义

#define NUM 10
const int NUM = 10;

用#define NUM 10 的时候在预处理的时候NUM已经被替换成10了,NUM就没有被编译器看到过,当我们得出一些编译错误信息的时候,可能会带来一些困惑,如果这个NUM不是写在你的头文件里你你们不知道这个10代表什么意思,解决方法是用常量替代上面的宏

  1. const有类型,可进行编译器类型安全检查,#define无类型,不可进行类型检查

  2. const有作用域,而#define不重视作用域,宏不能作为命名空间,结构体,类的成员

    namespace A{
    	#define NUM  100     //无效,宏不能作为结构体,命名空间,类多成员
        const int NUM1 = 100;
    }
    cout<<A::NUM<<endl; // 报错,找不到
    cout<<A::NUM1<<endl; //找得到
    

左值与右值

左值:可以放在等号左边的,能够取地址,具有名字

int a = 0;						//变量名,a是左值
void func(int &a)				//左值引用的函数调用,a是左值
++a;--a;					   //前置自增或自减,因为++a是先算a+1,赋值给a,这个a是可以取地址的,所有是左值
++a = 10;					   //所有此式可以通过编译
(a = 9) = 100;					//赋值运算或者复合赋值运算也可以当作左值
A *a = &p;
*a = xxx;						//解引用也是左值

右值:只能放在右边的值,不能取地址,不具备名字

a = 9						//字面值,右值
a = Max(i,j);				//返回非引用函数调用,右值
a++;a--;			//后置自增或自减,右值,因为是先算a+1,然后把a+1这个值返回出去,而这个值无地址无名是个右值
a = b - c;			//算术表达式,右值
a = b && c;			//逻辑表达式,右值

当右值被作为拷贝函数参数时,将被作为将亡值,如果类中有移动拷贝构造时优先调用移动拷贝构造,否则调用拷贝构造

#include<iostream>

using namespace std;

class A {
public:
    A() {
        cout << "AStruct " << this << endl;
    }
    ~A() {
        cout << "~A" << endl;
    }
    A(const A&) {										//拷贝构造
        cout << "cpStruct" << this << endl;
    };
    //A(A&&) {											//移动拷贝构造,参数列表中写的A&&是右值引用
    //    cout << "moveStruct" << this << endl;
    //}
};

A test() {
    A temp;
    return temp;
}

int main() {
    A a = test();			//函数调用返回值是一个右值,此时A类中移动拷贝构造被注释,此时调用的是拷贝构造
    return 0;
}

运行结果如下

AStruct 000000A466EFFBA4
cpStruct000000A466EFFCE4
~A
~A

如果把注释消掉,运行结果如下:

AStruct 000000CA73F1F804
moveStruct000000CA73F1F944
~A
~A

此时调用的是移动构造

用右值来调用拷贝构造时,右值被称为将亡值,目的是触发移动构造或移动赋值构造,并进行资源转移,之后调用析构函数(资源转移后调用析构,所以被称为将亡值)

左值引用和右值引用

int a = 100;
int& b = a;
const int& c = 10;
int&& d = 10;					//右值引用,指向右值,但是d是一个左值
d++;
cout << d << endl;				
int&& e = move(a);				//右值引用,运用move把左值a变成一个右值,但是e是一个左值
e++;
cout << e << endl;

运行结果如下:
11
101

常量也可以通过右值引用进行修改

声明出来的左值引用或者右值引用都是左值

左值引用时对左值的引用,右值引用(c++11新特性)是对右值的引用

​ 也有特例,左值引用也可以引用右值:const 左值引用 可以引用右值,但是这个局限,不能修改这个值(但是我们用引用的目的是通过引用修改变量,所以这个方法没什么用,所以c++11引入了右值引用来解决这个问题,让右值也可以被修改),

​ 右值引用也可以这样引用左值:用std::move(lvalue)函数,这个函数可以把左值转化成右值,如果这个左值的类型实现了移动构造或者移动赋值构造,则这个右值即使一个将亡值

左值引用避免了对象的拷贝,比如再函数传参用引用起别名来代替值传递

右值引用实现了移动语义和实现完美转发

移动语义:

#include<iostream>

using namespace std;

class A {
public:
    int* p;
    A() {
        p = new int(10);
        cout << "A():p=" << p << endl;
    }
    ~A() {
        delete p;
        p = nullptr;
        cout << "~A()" << endl;
    }
    A(const A& a) {								//深拷贝,重新分配资源
        //p = a.p;								//当不适用拷贝构造直接进行赋值的话
        //a.p = nullptr;						//对a的p指针置空会报错,因为a是一个常量引用,不可改变,所以a.p会存在出现野指针的问题,会导致内存泄漏
        p = new int(10);
        memcpy(p, a.p, sizeof(int));
        cout << "cpStruct:p=" << p << endl;
    };
    /*A(A&& a) {
        this->p = a.p;
        a.p = nullptr;
        cout << "moveSturct:p=" << endl;
    }*/
};


int main() {
    A a;
    A b(a);
    return 0;
}

运行结果如下:

A():p=000001F7EA9A71F0
cpStruct:p=000001F7EA9A6AF0
~A()
~A()

这个时候a作为左值调用的是拷贝构造,在堆里new了一个与原对象一样的对象,是深拷贝,所以两个对象地址不一样

如果改成这样

#include<iostream>

using namespace std;

class A {
public:
    int* p;
    A() {
        p = new int(10);
        cout << "A():p=" << p << endl;
    }
    ~A() {
        delete p;
        p = nullptr;
        cout << "~A()" << endl;
    }
    A(const A& a) {
        p = new int(10);
        memcpy(p, a.p, sizeof(int));
        cout << "cpStruct:p=" << p << endl;
    };
    A(A&& a) {
        this->p = a.p;
        a.p = nullptr;
        cout << "moveSturct:p=" << p << endl;
    }
};


int main() {
    A a;
    A b(move(a));				//让a变成一个右值,实现右值引用,在类里会调用移动构造而不是拷贝构造
    cout << a.p << endl;
    return 0;
}

结果如下:

A():p=0000024A277E6CB0
moveSturct:p=0000024A277E6CB0
0000000000000000
~A()
~A()

此时b与之前的a完全一样,而a在调用完移动构造后销毁.

当用move把a变成右值是,a变成将亡值,会触发移动构造,即在消亡之前把a赋给b,完成后自己灭亡

相比与拷贝构造,移动构造性能更强,类似与两个冰箱,a冰箱有一只大象,冰箱也想要有一只大象,拷贝构造的做法是,在外界再找一个一摸一样的大象放在冰箱里,而移动构造的做法是,把a中大象移动到b冰箱去

移动语义的用法:

  1. 对象赋值时避免资源重新分配

  2. stl容器应用:

    vector<A> vec;
    vec.push_back(A());
    

    A()作为一个右值,在push_back(A a)函数中形参进行值传递时 A a = A();触发了移动构造,直接把A()的值让vector容器接管,相比之前效率更高

完美转发:不仅能准确的转发参数的值,还能保证被转发的参数左右值属性不变

void func(int &n){
	cout<<"lvalue="<<n<<endl;
}
void func(int &&n){								
    cout<<"rvalue="<<n<<endl;
}
template<typename T>
void revoke(T &&t){								//T &&t或者auto &&t是万能引用,可以使接左值也可以接右值
	func(forward<T>(t));						//forward(t)保持参数的左右值属性不变
}

int main(){
    int i = 10;
    revoke(i);									//i作为一个左值调用revoke
    revoke(10);									//10作为一个右值调用revoke
    return 0;
}

结果如下

lvalue=10
rvalue=10

如果是一个不具体的类型加上&&(此例中是 T &&t)那这个引用时万能引用,如果时具体的类型比如int &&t就是右值引用,forward()是保持这个值的左右值属性不变,如果参数是左值则会转换成T类型的左值,如果参数是右值,则会转换T类型的右值

智能指针

在c++11之前,指针管理有几个困境

  1. 资源释放了,指针没有置空

     1.  野指针:指针指向堆中一个地址,这个地址delete掉了,但是指针没有制空(赋nullptr)
    
    1. 悬挂指针:多个指针指向一个堆里的一个地址,其中一个指针把这个地址delete掉了,也置空了,但其他的指针不知道,其他指针就是悬挂指针

    2. 踩内存:当上面两种情况下,野指针或者悬挂指针指向的地址被其他进程重新使用,这些指针可以对意料之外的指向的地址操作,这叫做踩内存

  2. 没有释放资源产生内存泄漏

    1. new与delete次数不匹配

    2. 次数匹配,但在运用多态时,用父类构建子类对象时,父类没有虚析构,析构函数就静态绑定了父类析构,子类生命周期结束时,只会调用父类析构,子类的属性就得不到释放造成了内存泄漏

      #include<iostream>
      
      using namespace std;
      
      class A {
          int i;
      public:
          ~A() {
              cout << "父类析构调用" << endl;
          }
      };
      class B :public A {
          int j;
      public:
          ~B() {
              cout << "子类析构调用" << endl;
          }
      };
      
      int main() {
          A* b = new B();
          delete b;
          return 0;
      }
      
      

      运行结果如下:

      父类析构调用

      应改成虚析构,在父类析构前加入virtual,让他在运行时绑定子类析构,因为子类析构结束后会调用父类析构

      #include<iostream>
      
      using namespace std;
      
      class A {
          int i;
      public:
          virtual ~A() {
              cout << "父类析构调用" << endl;
          }
      };
      class B :public A {
          int j;
      public:
          ~B() {
              cout << "子类析构调用" << endl;
          }
      };
      
      int main() {
          A* b = new B();
          delete b;
          return 0;
      }
      
      

      子类析构调用
      父类析构调用

  3. 重复释放资源,引发coredump

解决方案:智能指针:

STL

string库内部的声明如下

//构造函数
string();								//创建一个空的字符串
string(const string& str);				//拷贝构造,用一个string对象初始化
string(const char* s);					//用一个字符串初始化
string(int n,char c);					//初始化字符串为n个字符c

//基本赋值操作
string& operator=(const char* s);		//重载=号,结果返回这个这个对象的引用,所有可以链式操作,就像cout一样在这里是(s = "1234") = "asd";不报错,因为完成等号后的值返回的是一个引用,是个左值
string& operator=(const string& s);
string& operator=(char c);

string& assign(const char* s);			//字符串赋值赋值
string& assign(const string &s);
string& assign(const char* s ,int n);    //把s字符串前n个赋给此对象
string& assign(int n ,int c);			//用n个字符c赋值给此对象
string& assing(const string&s,int start,int n); //将s的从start开始的n个字符赋值给对象

//字符串的存取操作
char& operator[](int n); 				//通过[]来取字符,这个可能会数组越界
char& at(int n);						//通过at方法获取字符,内涵异常处理不会越界

//字符串的拼接操作
string& operator+=(const string& str);		//重载+=,实现拼接
string& operator+=(const char* s);		
string& operator+=(char c);

string& append(const char*s);				//将s追加到该字符串尾部
string& append(const char*s,int n);			//把s的前n个字符追加到字符串尾部
string& append(const string*s);
string& append(const string*s,int pos,int n);//把s从pos开始的n个字符追加到字符串上
string& append(int n ,char c);				//追加n个字符c

//字符串的查找替换
int find(const string& str,int pos = 0) const;		//查找第一次出现str的位置,pos为参数默认为0,,结尾加cosnt代表这是个只读函数,不会改变数据成员,可以提高程序健壮性
int find(const char* s,int pos = 0) const;
int find(const char* s,int pos = 0, int n) const;	//查找前n个字符第一次出现s的位置
int find(char c,int pos = 0) const;					//查找字符c第一次出现的为
int rfind(const string& str,int pos = npos) const;		//逆序找
int rfind(const char* s,int pos = npos) const;
int rfind(const char* s,int pos =npos, int n) const;	
int rfind(char c,int pos = npos) const;	

string& replace(int pos,int n,const string& str);		//在字符串从pos开始后n位变成str
string& replace(int pos,int n,const char* str);

//大小比较
bool operator>(const string&s);						//> < == != 都可用
......
int compare(const string &s)const;					//按字符编码排序,大于返回1小于返回-1,=返回0
int compare(const char* s)const;

//提取子串
string substr(int pos = 0, int n = nops) const;	//返回前n个字符组成的字符串
    
//插入和删除
string& insert(int pos, const char* s);    		//插入字符串
string& insert(int pos, const string& s); 		
string& insert(int pos, int n, char c);  		//在指定位置插入n个字符c
string& erase(int pos, int n = npos);			//删除从pos开始的n个字符

vector

​ 在stl中除了string之外的所有容器都是类模板

​ vector是动态数组,每当size()的返回值(即容器的大小)即将超过容器容量时,capactiy()返回值(容量)会自增一倍(0,1,2,4,8,16,32…),当这个容器后面的空间没有需要动态增加的大小时,它的做法是找一个更大的内存空间,然后把原数据拷贝到新空间,并释放原空间

vector<int> v;
vector<int>::iterator it;  				//it是存放int类型vector容器的迭代器
int i;
v.push_back(i);						
v.pop_back(i);
v.front();
v.back();
v.begin();             //得到容器的起始迭代器指向首元素
v.end();				//得到容器的尾迭代器指向尾元素的下一个位置,
it++;					//迭代器重载了++ --表明迭代器到下一个迭代器的位置
*it						//代表it迭代器指向的对象

函数API

//构造函数
vector<T> v;
vector(v.begin(),v.end());//可以这样int arr[] = {1,2,3};vector<int>;v(arr,arr+sizeof(arr)/sizeof(int));				 
vector(n,elem);								//拷贝n个elem给自身			
vector(const vector &vec);

//赋值
assign(beg,end);							//将[beg,end)区间的数据赋值自身
assign(n,elem);
vector& operator=(const vector &vec);			//重载=
swap(vec);									//将vec与本容器内容互换
size();								//元素个数
empty();			//是否为空
resize(int num);			//重新指定长度为num,容器变短则删除超出的,变长则填默认值
resize(int num ,int elem);	//如上,elem是默认值
capacity();					//容器的容量
reserver(int len); 	//直接预留len个长度,预留位置不初始化

//存取操作
at(int idx);		
operator[];  
front();			//返回第一个数据元素
back();			

//插入和删除
insert(const iterator pos,int count,ele); // pos代表插入位置的迭代器,n为个数,ele是哪个数
push_back(ele);
pop_back();
erase(const iterator start,const iterator end);//删除迭代器从start到end'之间的元素
erase(const iterator pos); 				//删除迭代器指向的元素
clear();		//删除容器中所有

deque

​ 双端动态数组,其也实现了迭代器,但是复杂程度和vector不是一个量级的,尽可能的使用vector,升值进行排序可以先把数据赋值到vector中在vector中排完序后再赋回来还更快

deque的实现原理:

​ 有一个连续的地址被称为中控器,每个地址储存一个指针,指针指向缓冲区一小段连续的空间,deque的数据存在缓冲区中其中一小段连续空间中的一个空间,当这一小段空间填满了,中控器会再这个指针的上放或者下方再创建一个指针,指向缓冲区中另一个连续的小段地址,如果从头端压入,则数据放在这段连续地址从右往前数第一个空地址,如果从后方压入,则放在连续地址的从前往后数的第一个空地址

deque的API与vector大同小异,多了push_front(ele)和pop_front(ele);

stack

​ 栈 :先进后出的容器,没有迭代器,不支持遍历

pop();					//压出
push(ele);				//压入元素
top();					//栈顶元素
empty();				//判断堆栈是否为空
size();					//栈大小

queue

​ 队列容器:先进先出,出数据一方叫对头,入数据较队尾,没有迭代器,不支持遍历行为

API与stack大同小异

list

​ 链表:储存非连续非顺序的一种数据结构,是一个双向链表,拥有双向迭代器,不用管具体细节,能用就像

API与deque大同小异,因为sort排序定义在algorithm头文件里,只支持随机访问容器不支持链表容器,所有list提供了内部sort成员函数

sort(l.begin(),l.end());

set

​ 集合:只有键值,所有元素的键值会自动排序,且不允许有两个相同的键值,有迭代器,但是是只读迭代器,不能中途改变,因为改变键值可能会导致set从有序变为无序,set和multiset底层都是用红黑树完成的是平衡二叉树

multiset与set完全相同,除了运行键值重复

API

swap(st);			//交换两个集合容器
insert(ele);			//插入ele
erase(pos);			//删除pos迭代器所指的元素,返回下一个元素的迭代器
erase(beg,end);		//删除[beg,end)的所有元素
erase(ele);				//删除值为ele的元素
find(key);			//查找key的个数

//更改set的排序规则
set<int,排序规则类> s1;		//排序规则是在尖括号里面是一个类型,所有只能通过仿函数定义规则(仿函数重载())
//如下
class MyCompare{
public:
	bool operator()(int v1,int v2){			//重载(),当调用MyCompare()的时候,执行这个操作
        return v1>v2;					//改成从小到大
    }    
}

set<int ,MyCompare> s1;			//当实例化的时候,类MyCompare实例化构造函数触发仿函数,实现排序规则的改变

//再更改set的排序规则的对象是自定义结果类型的时候,可以设置这个仿函数的类设置为友元类定义在这个自定义结构里
//比如
class Person{
	friend class MyCompare;
    .....
}

set<int>::const_iterator ret = s1.find(ele);      //find返回一个迭代器,因为set所有迭代器都是只读,所以用const_iterator来接,如果没找到这个值,返回s1.end();
if (ret != s1.end()){
	cout<<*ret<<endl;
}    

lower_bound(keyele);				//返回第一个大于等于keyele的元素的迭代器,下限
upper_bound(keylel);				//返回第一个大于keyele的元素的迭代器,上限
equal_range(keyele);				//返回容器中key与keyele相等的上下限的两个迭代器,返回值类型为pair
//如下
pair<set<int>::const_iterator,set<int>::const_iterator> p;
p = s1.equal_range(keyele);
if (p .first != s1.end()){
    cout<<"下限为"<<*(p.first)<<endl;
}
if (p .second != s1.end()){
    cout<<"上限为"<<*(p.second)<<endl;
}

pair

​ 队组:将一对值组合成一个值,可以有不同的数据类型,通过first,second访问

​ make_pair(type1,type2)返回一个队组

map

算法

//suanfa.h
//constexpr auto N = 10;

//auto randArr(int max) -> int(*)[N];

int* randArr(int size, int max);

void print(int* (*pf)(int size, int max), int size, int max);

void print(int* arr, int size);

void swap(int& a, int& b);

void Bubble_sort(int* arr, int size);

void Select_sort(int* arr, int size);

void Insert_sort(int* arr, int size);

void Merge_sort(int* arr, int L ,int R);

void Merge(int* arr, int L,int M, int R);

void Quick_sort(int* arr, int L, int R);

int Quick(int* arr, int L, int R);

int Fenzhi_get_max(int* arr, int L, int R);

void Hanoi(int nums, char a, char b, char c);

//main.cpp
#include<iostream>
#include"suanfa.h"

using namespace std;

int main() {
	
	int size,max;
	cin >> size >> max;
	int * arr = randArr(size, max);
	time_t t1 = time(0);
	Merge_sort(arr, 0, size - 1);
	time_t t2 = time(0);
	cout << t2 - t1 << endl;
	
	//print(arr, size);
	//cout << "-----------" << endl;
	//cout << Fenzhi_get_max(arr,0,size - 1);

	//int n;
	//cin >> n;
	//Hanoi(n, 'a', 'b', 'c');

	return 0;
}

//suanfa.cpp
#include<iostream>
#include"suanfa.h"
#include<time.h>
using namespace std;

//constexpr auto N = 10;

//auto randArr(int max) -> int(*)[N]
//{
//	srand(time(0));
//	int(*arr)[N] = (int(*)[N]) new int[N]();
//	for (int i = 0; i < N; i++) {
//		(*arr)[i] = rand() % max;
//	}
//	return arr;
//}

int* randArr(int size, int max) {
	srand(time(0));
	int* arr = new int[size]();
	for (int i = 0; i < size; i++) {
		arr[i] = rand() % max;
	}
	return arr;
}

void print(int* (*pf)(int size, int max), int size, int max) {
	for (int i = 0; i < size; i++) {
		cout << pf(size, max)[i] << endl;
	}
}

void print(int* arr, int size)
{
	for (int i = 0; i < size; i++) {
		cout << arr[i] << endl;
	}
}

void swap(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}

void Bubble_sort(int* arr, int size)
{
	for (int i = 0; i < size - 1; i++) {
		for (int j = 0 ; j < size - i - 1; j++) {
			if (arr[j] > arr[j + 1]) {
				swap(arr[j], arr[j + 1]);
			}
		}
	}
}

void Select_sort(int* arr, int size)
{
	
	for (int i = 0; i < size - 1; i++) {
		int min = i;
		for (int j = i + 1; j < size ; j++) {
			if (arr[j] < arr[min]) {
				min = j;
			}
		}
		swap(arr[i], arr[min]);
	}
}

void Insert_sort(int* arr, int size)
{
	int ok = 1;
	for (int i = 0; i < size - 1; i++) {
		for (int j = i + 1; j > 0 && ok; j--) {
			if (arr[j] < arr[j - 1]) {
				swap(arr[j], arr[j - 1]);
			}
			else {
				ok == 0;
			}
		}
	}
}

void Merge_sort(int* arr, int L, int R)
{
	if (L == R) return;
	int M = L + (R - L) / 2;
	Merge_sort(arr, L, M);
	Merge_sort(arr, M + 1, R);
	Merge(arr, L, M, R);
}

void Merge(int* arr, int L,int M, int R)
{
	int* temp = new int[R - L + 1];
	int i = 0;
	int p1 = L;
	int p2 = M + 1;
	while (p1 <= M && p2 <= R) {
		temp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
	}
	while (p1 <= M) {
		temp[i++] = arr[p1++];
	}
	while (p2 <= R) {
		temp[i++] = arr[p2++];
	}
	for (int c = 0; c < R - L + 1; c++) {
		arr[c + L] = temp[c];
	}
	delete[] temp;
}

void Quick_sort(int* arr, int L, int R)
{
	if (R - L <= 0) return;
	else {
		int M = Quick(arr, L, R);
		Quick_sort(arr, L, M - 1);
		Quick_sort(arr, M + 1, R);
	}
}

int Quick(int* arr, int L, int R)
{
	int point = R;
	int p1 = L , p2 = R - 1;
	while (1) {
		while (arr[p1] < arr[point] && p1 < point) {
			p1++;
		}
		while (arr[p2] > arr[point] ) {
			p2--;
		}
		if (p1 >= p2) {
			break;
		}
		else {
			swap(arr[p1], arr[p2]);
			p1++;
			p2--;
		}
	}
	swap(arr[p1], arr[point]);
	return p1;
}


int Fenzhi_get_max(int* arr,int L , int R)
{
	if (R < L) return -1;
	if (R == L) return arr[L];
	if (R - L == 1) return arr[L] > arr[R] ? arr[L] : arr[R];
	int M = L + (R - L) / 2;
	int last_L = Fenzhi_get_max(arr, L, M);
	int last_R = Fenzhi_get_max(arr, M + 1, R);
	return last_L > last_R ? last_L : last_R;
}

void Hanoi(int nums, char a, char b, char c)
{
	static int i = 1;
	if (nums == 1) {
		cout << "第" << i << "次" << a << "->" << b << endl;
		i++;
	}
	else {
		//把nums-1个圆盘从起始盘搬到辅助盘上去
		Hanoi(nums - 1, a, c, b);
		//把最大的圆盘从起始盘搬到目标盘去
		cout << "第" << i << "次" << a << "->" << c << endl;
		i++;
		//把nums-1个圆盘从辅助盘搬到目标盘上去
		Hanoi(nums - 1, c, b, a);
	}
}







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值