每日算法之2
1.之两个非递减的数组合并为一个数组保持依然有序
//问题描述:两个非递减数组,A,B,A具有足够的内存容纳B,要求将两个数组合并为一个数组,保持数组依然非递减
//算法分析:从B数组中第一个元素开始,依次与数组A的尾部至头部开始扫描比较,如果小于,则后移一位,下标前移一位,继续比较,直到找到非小于的位置,将其插入当前的后一位;遍历完B数组,则循环结束
//算法实现;
void mergeSort(int A[],int B[],int a_length,int b_length){
if(A==NULL || B==NULL ){
cout<<"error!"<<endl;
return;
}
if(a_length>b_length){
int j=a_length-b_length-1;//表示指向A数组的下标
int i;//表示指向B数组的下标
int k=a_length-1;
for(i=b_length-1;i>=0 && j>=0;--i){//表示遍历B数组的元素
for(j;j>=0;--j){
if(B[i]>=A[j]){
A[k--]=B[i];
break;
}
else
A[k--]=A[j];
}
}
if(i+1>=0){//将B数组剩余元素从头开始依次填入A数组,记得由于上次循环退出时,是自动减了1
for(int p=0;p<=i+1;++p){
A[p]=B[p];
}
}
}
}
int main(){
//测试用例
int a[10]={4,5,6,8,10,34};
int b[4]={3,5,9,18};
mergeSort(a,b,10,4);
for(int i=0;i<10;++i)
cout<<a[i]<<" ";
}
//虽然使用了两个循环,但是没执行一次比较,必会干掉一个最大值放在后面,也就是说,从思路上就能判断出基本操作次数最坏情况下为两个序列的长度和,所以时间复杂度为O(n),即线性时间
//其他优秀的算法代码分析:
/**
* 合并两个有序数组,合并后仍然有序
* @param a 要合并的数组A
* @param b 要合并的数组B
* @param c 合并后的数组C
*/
void merge(int a[] ,int b[],int c[]){
//1.传入函数的参数必须考虑周全,传入什么参数,有什么作用,考虑是值传递还是引用传递,需不需
//要加const常量进行限定
int lengthA = a.length;
int lengthB = b.length;
//2.首先将此函数需要使用的局部变量定义好,标识符要有实际意义,尽量避免使用i,k,l,否则
//阅读你的代码真是一件头疼的事情
int indexA = 0;
int indexB = 0;
int indexC = 0;
//3.使用循环必须充分考虑好退出条件,是否存在其他退出循环的情况
while(indexA < lengthA && indexB < lengthB){
if(a[indexA] < b[indexB]){
c[indexC++] = a[indexA++];
}else{
c[indexC++] = b[indexB++];
}
}
//4.当退出循环时,这个遍历指针或者下标是否自动前移?
//此外,当循环结束,是否真的完成了?
while(indexA < lengthA){
c[indexC++] = a[indexA++];
}
while (indexB < lengthB) {
c[indexC++] = b[indexB++];
}
//5.使用循环最头疼的就是边界条件,只要记住一点,编号,第几号和个数,第几个不要混用就OK了
}
2.之链表操作-反向输出链表
链表的特点:是一种动态的数据结构,传入链表时,只需要传入头结点指针即可,不需要指定长度
//问题描述:输入一个链表,将一个带头结点的链表反转输出
//第一种方法
struct LNode{
int data;
struct LNode *next;
};
void reverse_list(LNode *A){
LNode *curLNode,*nextLNode,*tempLNode;//定义当前指针和下一个指针以及一个临时指针
curLNode=A->next;
nextLNode=curLNode->next;//进行初始化操作
curLNode->next=NULL;
while(nextLNode!=NULL){//当下一个节点为空时,退出循环
tempLNode=curLNode;
curLNode=nextLNode;
nextLNode=nextLNode->next;//每次反转之前需要后移一位,并使用临时变量记住上次的节点
curLNode->next=tempLNode;//将当前的节点指针反转指向临时节点
}
A->next=curLNode;//最后将头结点指针重新指向反转后的第一个节点
}
void createList(LNode *A,const int data[],int length){
//1.使用头插法创建一个链表,并将一个数组赋值给链表节点的数据域,由于使用的是头插法,所以数组的数据顺序
//是逆序的!!
LNode *firstLNode;//定义一个指向链表第一个节点的指针
LNode *newLNode;//定义一个需要插入个新节点指针
A=new LNode;//由于传入的只是一个指向节点的指针,所以创建时必须new出一个指针,并指向它!!!
A->next=NULL;//初始化头结点指针
for(int i=0;i<length;++i){
newLNode= new LNode;
newLNode->data=data[i];
firstLNode=A->next;
newLNode->next=firstLNode;
A->next=newLNode;//第一次虽然指向空节点,但正好将尾节点初始化为NULL,一举两得!!
}
//2.使用尾插法正向插入数组数据
LNode *lastLNode;
LNode *newLNode;
A=new LNode;
A->next=NULL;
lastLNode=A;//初始化尾节点
for(int i=0;i<length;++i){
newLNode= new LNode;
newLNode->data=data[i];
lastLNode->next=newLNode;
lastLNode=lastLNode->next;
}
lastLNode->next=NULL;//插入完毕必须初始化尾节点指针域
}
//打印链表
void printfList( LNode *A){
LNode *index;
for(index=A;index->next!=NULL;index=index->next){//由于是带头结点的链表
cout<<index->next->data<<" ";//index为当前指针节点,但是打印输出时却是下一个节点
}
cout<<endl;
}
//测试用例:
int main(){
int a[10]={1,2,3,4,5,6,7,8,9,10};
LNode *list;
createList(list,a,10);
printfList(list);
reverse_list(list);
printfList(list);
}
//第二种方法
//第一种方法需要修改链表,如果要求不能修改数据结构,改变策略,由于要求将链表逆序输出,可以先正向遍历
//将其存入一个栈中,利用栈的后进先出的特点,从而实现逆序
//算法实现:
void reversePrintfList_useListStack(LNode *A){//必须使用一个链表栈实现
LNode *indexA,*indexS;//链表A和S的指针迭代器
LNode *stack_t_LNode;//栈的头结点
LNode *new_s_LNode;//栈的新插入的节点
LNode *first_s_LNode;//表示栈的第一个节点
stack_t_LNode=new LNode;
stack_t_LNode->next=NULL;//初始化链表栈,必须使用头插法
for(indexA=A;indexA->next!=NULL;indexA=indexA->next){
new_s_LNode=new LNode;
new_s_LNode->data=indexA->next->data;//压入栈
first_s_LNode=stack_t_LNode->next;
new_s_LNode->next=first_s_LNode;
stack_t_LNode->next=new_s_LNode;//使用头插法连接到栈中
}
//将栈遍历输出
for(indexS=stack_t_LNode;indexS->next!=NULL;indexS=indexS->next){
cout<<indexS->next->data<<" ";
}
cout<<endl;
delete[] stack_t_LNode;
}
//第三种方法
//既然使用自定义链表栈实现反向输出,为何不使用一个简单的递归函数,直接两三行代码实现递归栈的反转输出
void reversePrintfList_useRecursion(LNode *A){
if(A!=NULL){
if(A->next!=NULL){
reversePrintfList_useRecursion(A->next);
}
cout<<A->data<<" ";
}
}//该办法有一个缺点,如果将带头结点的链表传入,头结点的data域也会被访问打印,解决办法只能再调用的时候,将头结点过滤掉!!!
//比如:
reversePrintfList_useRecursion(A->next);
3.之二叉树的三种遍历考察[重点]
//掌二叉树的八大遍历实现,构建;;掌握完全二叉树搜索树,最大小堆,红黑树,字典树
//使用先序遍历构建二叉树
typedef struct BTNode{
char data;
struct BTNode *lchild,*rchild;
}BTNode;
//逻辑存在严重的错误!!
//算法分析:使用先序遍历将字符串数组"ABDG##H###CE##F##"构建成一棵二叉树
void createBTree_usePreorder(BTNode *B,char *data){
int index_data;
index_data=0;
if(data[index_data]!='\0'){
if(data[index_data]=='#')
B=NULL;//表示为叶节点
else{
B=new BTNode;
B->data=data[index_data];
createBTree_usePreorder(B->lchild,data+1);
createBTree_usePreorder(B->rchild,data+1);
}
}
}
//二叉树的四种遍历方式:三种深度优先的递归实现和借助栈的非递归实现;
//一种借助队列实现广度优先的非递归实现
//1.先序遍历的递归实现和非递归实现
//递归实现:
void BTree_preorder(BTNode *B){
if(B!=NULL){
cout<<B->data<<" ";
BTree_preorder(B->lchild);
BTree_preorder(B->rchild);
}
}