链表
链表插入
// 有序链表中做插入
//
// 1、算法思想:对链表做遍历,p指向当前访问的节点,p1表示p的前驱。逐一比较p的数值与待插入的数据,遇到第一个大于等于待插入
// 数据时,遍历停止。此时p1与p之间,就是要插入的位置;
// 2、时间复杂度:在确定插入位置后,插入的操作很简单,只要修改几个指针即可,时间主要用在遍历以确定插入位置上,时间复杂度是
// O(n)。这一点与数组不同,即便在确定插入位置后,也需要移动若干次后才能完成数组中的插入
//
/
void insdata(LI *A,int data)
{
LI *p1,*p;//
LI *q;//用q指向新生的节点空间
p1=A;//
p=p1->next;//
while(p)//开始遍历链表A寻找合适的插入位置
{
if(p->data<data)//当前结点的数值比待插入数值要小的话,p与p1的指针要向后移动
{
p1=p;//
p=p->next;//
}
else//当前结点的数值大于等于待插入数值,比较结束
break;//
}
q=(LI *)malloc(sizeof(LI));//动态生成节点空间
q->data=data;//给改新结点数值域赋值
p1->next=q;//将该新结点插入到p1与p之间
q->next=p;//
}
链表去重
void delrepeat(LI *A)
{
LI *p1,*p;
if(A->next==NULL||A->next->next==NULL)
return;
p1=A->next;
p=p1->next;
while(p)
{
if(p->data==p1->data)
{
p1->next=p->next;
free(p);
p=p1->next;
}
else
{
p1=p;
p=p->next;
}
}
}
链表归并
// 有序链表归并
//
//1、算法思想:链表的归并或者交集,一般无所 谓异地或者本地,因为其插入和删除都不需要移动,本地或者异地在时间复杂度上没有区别。这里我们要求A=A or B,要求将B中的节点插入到A中,并释放掉重复结点,处理后A包含二者并集结点,B为空。
//2、算法实现:该算法涉及到在A中做插入,在B中删除与A重复的节点。算法与数组归并类似,用pa和pb分别遍历链表A和B,比较pa和pb指向的节点数值,如果pa的数值小,此时不做插入,pa指针向后移动;如果pa和pb的数据相等,也不做插入,将pb指向的节点删除掉,然后pa和pb同时向后移;如果pa的数据大于pb,此时要将pb插入到pa之前,然后pb的指针要向后;当pa或者pb有一个已经为假即已经指向NULL,比较结束。此时看看B中是否有剩余节点,将剩余节点直接放在链表A中,归并结束。
// 2、时间复杂度:整个过程,每个结点不会被重复遍历或者操作,因此时间复杂度是O(m+n)
// 3、空间复杂度:本地实现,需要的额外的空间与链表规模无关,空间复杂度是O(1)
// 4、应用:如果是求交集,A=A anb B,与上述算法类似,只不过是在A中做删除。当pa的数值比pb小时,要把pa删掉;当pa的数值比pb大时,pb的指针向后移动;当pa与pb的数值相等时,不做删除,两个指针pa和pb同时向后移动。在pa和pb中有一个为假时,比较结束。
// 之后要判断A中的指针pa是否为真,如果是真表示A中还剩余一些未被比较的节点,这些结点都不是交集元素,要删除掉;如果pa已经为
// 假,那么算法可以结束
// 5、注意事项:在有序单链表中做插入,首先要遍历链表,找到第一个大于或等于待插入数据的节点时停下来,应该再此结点之前做插入
// 因此,在遍历时,不仅需要指针p指向当前结点,也要指针p1指向p的前驱,插入就是在p1和p之间进行的
//
/
void Merge(LI *A,LI *B)
{
LI *pa,*pa1;//pa用来在A中遍历,指向当前访问的节点,pa1指向pa的前驱,方便做插入
LI *pb,*pb2;//pb用来在B中遍历,指向当前访问的节点,pb2是pb的后继,因为把若把pb插入到链表A中,pb的next域会发生变化,如果想要
//对pb的后续节点继续做处理,需要将其后继实现保存到pb2
pa1=A;//给pa1赋初值
pa=pa1->next;//给pa赋初值
pb=B->next;//给pb赋初值
B->next=NULL;//因为处理结束后,B的所有结点要么已经被插入到A中,要么被释放掉,因此应该是个空链表
while(pa&&pb)//如果pa与pb同时为真
{
pb2=pb->next;//保存pb的后继(注意这个赋值要放在循环内,确保pb为真,它才会有后继next)
if(pa->data<pb->data)//如果A中元素比较小,不做插入
{
pa1=pa;//向后移动
pa=pa->next;//向后移动
}
else if(pa->data==pb->data)//如果A和B的元素相等,要把B中的节点释放掉,同时更新pa、pb及相应指针
{
pa1=pa;//向后移动
pa=pa->next;//向后移动
free(pb);//把B中的与A相同的节点释放掉
pb=pb2;//重新给pb赋值
}
else//如果A的元素比B的大,此时要把B的节点插入在pa1与pa之间
{
pa1->next=pb;//将pb插入到pa1后边
pb->next=pa;//此时pb成为pa的前驱了
pa1=pb;//更新pa1的指向,使其确保在pa的前驱的位置上
pb=pb2;//更新pb的指向
}
}
if(pb)//比较结束后,如果在B中有剩余的未被比较的元素
pa1->next=pb;//将这些剩余的节点直接链接在A的后边
///求A=A and B ///
/*LI *pa,*pa1,*pb;
pa1=A;pa=pa1->next;
pb=B->next;
while(pa&&pb)
{
if(pa->data<pb->data)
{
pa1->next=pa->next;
free(pa);
pa=pa1->next;
}
else if(pa->data>pb->data)
pb=pb->next;
else
{
pa1=pa;
pa=pa->next;
pb=pb->next;
}
}
if(pa)
pa1->next=NULL;
while(pa)
{
pa1=pa->next;
free(pa);
pa=pa1;
}*/
}
链表销毁
// 链表销毁
//
// 1、算法思想:对链表做遍历,p指向当前访问的节点,p2表示p的后继。将p指向的节点逐一释放。释放后p将没有指向,如果想对其后
// 的节点继续做释放,要实现将其后继保存到p2。
// 2、注意事项:带头链表,空链表是指头结点的 next指向NULL,并不是连头结点也释放销毁掉。同时要注意,若指针指向的空间被释放
// 后,该指针将没有指向,并不是自动指向NULL。没有指向的指针是不能使用的,必须重新赋值有了指向后,方可使用
//
/
void dellist(LI *A)
{
LI *p,*p2;
p=A->next;
A->next=NULL;
while(p)
{
p2=p->next;
free(p);
p=p2;
}
}
数组
数组归并
// 有序数组本地归并(A=A or B)
//
// 1、算法基本思想:将B中的元素插入到A中,要找到合适的位置做插入,为了保证数组A存储的连续性,插入需要做移动
// 2、算法实现:两个计数器i和j分别在数组A和B中做遍历,还需要一堵无形的墙k记录数组A中当前有效元素个数。在遍历过程中,
// 比较a[i]和b[j]的数值,如果a[i]<b[j],表示当前i的位置不能插入b[j],因此i++;如果a[i]==b[j],表示不需要再插入b[j],因此
// i++,j++;当a[i]>b[j],表示b[j]应该插入在当前数组A的i的位置上,此时将A中从最后一个元素开始,到a[i]逐个右移一位,将
// i这个位置空出来,然后把b[j]放入a[i],然后,j++,i++,k++,表示数组A中增加了一个新元素。当i和j有一个越界,比较结束。如果数组B中还有若干剩余元素,将其直接复制到A中,归并结束
// 3、时间复杂度:最坏情况下,数组B中的所有元素都比A中的元素小,每次都需要把A中的元素后移一位,因此移动的次数是O(m*n),
// 每个元素最多只会比较一次,因此时间复杂度是O(m*n);
// 4、空间复杂度:该算法本地实现,只需要几个变量,空间复杂度是O(c)
// 5、应用1:如果是求交集,即A=A and B,比较过程是一样的,但是不是做插入,而是做删除。还是要用到上述去重的那个算法思想,用一堵无形的墙将数组A分成左右两边,左边是两个数组的交集元素,我们的任务就是把墙右边的、交集元素移动到墙左边来。初始墙的
// 位置k=0。用这种方法每个元素最多只会被比较一次并移动一次,因此时间复杂度是O(m+n)
//
/
int Merge1(int *a,int *b,int m,int n)
{
int i,j,k;//计数器i,j分别在数组a和b中遍历,k表示当前数组a中有效元素个数,初始值为m
int h;//做插入时,数组a中的相应元素逐个后移一位,用h做计数器
for(i=0,j=0,k=m;i<k&&j<n;)//注意循环终止条件
{
if(a[i]<b[j])//当前a[i]小,b[j]不能插入
i++;
else if(a[i]==b[j])//二者相等,也不做插入
{
i++;
j++;
}
else//在数组a的i这个位置上要插入b[j]
{
for(h=k-1;h>=i;h--)//从a中当前最后一个元素开始,到元素a[i],
a[h+1]=a[h];//逐个后移一位
a[i]=b[j];//空出i这个位置,将b[j]插入
i++;//当前i的位置是新插入的b[j],所以将计数器i加1
j++;//b[j]已经做完处理,因此j加1
k++;//数组a中新增一个元素,墙的位置后移一位
}
}
while(j<n)//如果数组b中还有剩余的未做比较的元素,直接复制到数组a
{
a[k]=b[j];
j++;
k++;
}
return k;//返回数组a中实际包含的元素个数
///有序数组本地求交集///
/*int i,j,k;
for(i=0,j=0,k=0;i<m&&j<n;)
{
if(a[i]<b[j])
i++;
else if(a[i]>b[j])
j++;
else
{
a[k]=a[i];
k++;
i++;
j++;
}
}
return k;*/
}
数组冒泡排序
void sortdata(int *a,int n)//冒泡排序,后边第九章时会专门讲排序算法
{
int i,j,temp;
for(i=0;i<n-1;i++)
{
for(j=0;j<n-i-1;j++)
{
if(a[j]>a[j+1])
{
temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
}
}
}
回文匹配
用书上42页的方法
开辟三列,一列是步骤,一列是符号栈,一列是数字栈
- 当遇到数字,符号,入栈
- 当栈顶的优先级高于当前的优先级,先将符号栈弹栈一个栈顶元素,再在数字栈弹出两个数字,计算之后再入栈
- 当遇到()时候,反复的弹栈计算
例子;
表达式:2+9*(8-9/4*2)+7
三角矩阵问题
考察高中知识点:
S n = a 1 ∗ n + n ∗ ( n − 1 ) ∗ d S_n=a_1*n+n*(n-1)*d Sn=a1∗n+n∗(n−1)∗d
注意,n是i-1,表示前i-1行的所有元素;
然后计算该元素是在第j列的哪一个位置
对于如下形式:
( a 1 , 1 a 1 , 2 a 1 , 3 a 1 , 4 a 1 , 5 a 2 , 1 a 2 , 2 a 2 , 3 a 2 , 4 a 2 , 5 a 3 , 1 a 3 , 2 a 3 , 3 a 3 , 4 a 3 , 5 a 4 , 1 a 4 , 2 a 4 , 3 a 4 , 4 a 4 , 5 a 5 , 1 a 5 , 2 a 5 , 3 a 5 , 4 a 5 , 5 ) \begin{pmatrix} a_{1,1} & a_{1,2}&a_{1,3} &a_{1,4} &a_{1,5} \\ a_{2,1} & a_{2,2}&a_{2,3} &a_{2,4} &a_{2,5} \\ a_{3,1} & a_{3,2}&a_{3,3} &a_{3,4} &a_{3,5} \\ a_{4,1} & a_{4,2}&a_{4,3} &a_{4,4} &a_{4,5} \\ a_{5,1} & a_{5,2}&a_{5,3} &a_{5,4} &a_{5,5} \end{pmatrix} a1,1a2,1a3,1a4,1a5,1a1,2a2,2a3,2a4,2a5,2a1,3a2,3a3,3a4,3a5,3a1,4a2,4a3,4a4,4a5,4a1,5a2,5a3,5a4,5a5,5
倘若是:
( a 1 , 1 a 2 , 1 a 2 , 2 a 3 , 1 a 3 , 2 a 3 , 3 a 4 , 1 a 4 , 2 a 4 , 3 a 4 , 4 a 5 , 1 a 5 , 2 a 5 , 3 a 5 , 4 a 5 , 5 ) \begin{pmatrix} a_{1,1} & \\ a_{2,1} & a_{2,2}\\ a_{3,1} & a_{3,2}&a_{3,3} \\ a_{4,1} & a_{4,2}&a_{4,3} &a_{4,4}\\ a_{5,1} & a_{5,2}&a_{5,3} &a_{5,4} &a_{5,5} \end{pmatrix} a1,1a2,1a3,1a4,1a5,1a2,2a3,2a4,2a5,2a3,3a4,3a5,3a4,4a5,4a5,5
( a 1 , 1 a 1 , 2 a 1 , 3 a 1 , 4 a 1 , 5 a 2 , 1 a 2 , 2 a 2 , 3 a 2 , 4 a 3 , 1 a 3 , 2 a 3 , 3 a 4 , 1 a 4 , 2 a 5 , 1 ) \begin{pmatrix} a_{1,1} & a_{1,2}&a_{1,3} &a_{1,4} &a_{1,5} \\ a_{2,1} & a_{2,2}&a_{2,3} &a_{2,4} \\ a_{3,1} & a_{3,2}&a_{3,3} \\ a_{4,1} & a_{4,2} \\ a_{5,1} \end{pmatrix} a1,1a2,1a3,1a4,1a5,1a1,2a2,2a3,2a4,2a1,3a2,3a3,3a1,4a2,4a1,5
则它的j直接就是本身的位置;然后前面正常高中计算即可;
对于在右边的,比如:
( a 1 , 1 a 1 , 2 a 1 , 3 a 1 , 4 a 1 , 5 a 2 , 2 a 2 , 3 a 2 , 4 a 2 , 5 a 3 , 3 a 3 , 4 a 3 , 5 a 4 , 4 a 4 , 5 a 5 , 5 ) \begin{pmatrix} a_{1,1} & a_{1,2}&a_{1,3} &a_{1,4} &a_{1,5} \\ & a_{2,2}&a_{2,3} &a_{2,4} &a_{2,5} \\ & &a_{3,3} &a_{3,4} &a_{3,5} \\ & & &a_{4,4} &a_{4,5} \\ &&& &a_{5,5} \end{pmatrix} a1,1a1,2a2,2a1,3a2,3a3,3a1,4a2,4a3,4a4,4a1,5a2,5a3,5a4,5a5,5
则在列式j-i+1;
还有一种好像是i+j-N;
压缩存储&三元组
现有一个6行5列的三元组表示的稀疏矩阵A如下图所示。如果要将其做快速转置,请写出所需的辅助数据结构,以及在转置过程中数据结构的变化以及求解过程。
\
本题需要额外开辟两个辅助数组,cnum和cpot,cnum用于存储当前每一列的非零元素个数,cpot用于存储当前每一列中第一个非零元素所在的位置。
观察上图,容易得到:
1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|
cnum | 1 | 2 | 2 | 1 | 2 |
接下来,有:cpot[1]=1;
此外,有:cpot[i]=cpot[i-1]+cnum[i-1];
计算之后得到:
1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|
cnum | 1 | 2 | 2 | 1 | 2 |
cpot | 1 | 2 | 4 | 6 | 7 |
- 对源三元组进行遍历,每一次读取其列号j,其转置之后的位置即在:cpot[j]
- 然后为了保证在后边遍历到同一列的值的时候仍然能正常进行,在第一步遍历之后,cpot[j]++,这样剩余的元素则在属于它的位置
- 重复以上步骤,即可通过三元组进行快速地稀疏矩阵的转置。
树
二叉树
//定义树的节点
typedef struct Note
{
char data;
struct Note *left,*right,*pa;
}BT;
二叉树的遍历
先序
void preorder(BT *T)
{
//递归
/*if(T)
{
printf("%3c",T->data);
preorder(T->left);
preorder(T->right);/ }*/
//非递归:利用栈
BT *t[N],*p; //栈是反过来的,先进后出
int top=0;
t[top]=T;
while(top>=0)
{
p=t[top--];
printf("%3c",p->data);
if(p->right)
t[++top]=p->right;
if(p->left)
t[++top]=p->left;
}
}
void preoder(BT *T)
{
BT *t[N],*p;
int top=0;
t[top]=T;
while(top>=0)
{
p=t[top];
top--;
printf("%3c",p->data)
if(p->right)
{
top++;
t[top]=right;
}
if(p->left)
{
top++;
t[top]=left;
}
}
}
void preorder(BT *T)
{
BT *t[N],*p;
int top=0;
t[top]=T;
while(top>=0)
{
p=t[top];
top--;
if(p->right)
{
top++;
t[top]=p->right;
}
if(p->left)
{
top++;
t[top]=p->left;
}
}
}
void preorder(TR *T)
{
TR *t[N],*p;
int top=0;
t[top]=T;
while(top>=0)
{
p=t[top];
top--;
printf("%3c",p->data);
if(p->right)
{
top++;
t[top]=p->right;
}
if(p->left)
{
top++;
t[top]=p->left;
}
}
}
中序
void inorder(BT *T)
{
//递归
/*if(T)
{
inorder(T->left);
printf("%3c",T->data);
inorder(T->right);
}*/
//非递归:利用栈
BT *t[N],*p=T;
int top=-1;
while(top>=0 || p)
{
while(p)
{
t[++top]=p;
p=p->left;
}
p=t[top--];
printf("%3c",p->data);
p=p->right;
}
}
后序
void postorder(BT *T)
{
//递归
/*if(T)
{
postorder(T->left);
postorder(T->right);
printf("%3c",T->data);
}*/
//非递归:利用栈
BT *t[N],*p=T,*b;
int top=-1;
do
{
while(p)
{
t[++top]=p;
p=p->left;
}
b=NULL;
while(top>=0)
{
p=t[top];
if(p->right==b)
{
top--;
printf("%3c",p->data);
b=p;
}
else
{
p=p->right;
break;
}
}
}while(top>=0);
}
层次
void layer(BT *T)
{
//通过队列实现
BT *q[N],*p;
int front=0,rear=0;
q[rear++]=T;
while(front!=rear)
{
p=q[front++];
printf("%3c",p->data);
if(p->left)
q[rear++]=p->left;
if(p->right)
q[rear++]=p->right;
}
}
二叉树的应用
int height(BT *T)
{
int h1,h2;
if(!T)
return 0;
else
{
h1=height(T->left);
h2=height(T->right);
return h1>h2?h1+1:h2+1;
}
}
int leaf(BT *T)
{
int h1,h2;
if(!T)
return 0;
else if(!T->left && !T->right)
return 1;
else
{
h1=leaf(T->left);
h2=leaf(T->right);
return h1+h2;
}
}
void findpath(BT *T)
{
char p[N];
allpath(T,p,0);
}
void allpath(BT *T,char *path,int n)
{
递归(先序)
int i;
if(T)
{
path[n]=T->data;
if(!T->left && !T->right)
{
for(i=0;i<=n;i++)
printf("%3c",path[i]);
printf("\n");
}
else
{
allpath(T->left,path,n+1);
allpath(T->right,path,n+1);
}
}
//非递归(后序)
/*BT *t[N],*p=T,*b;
int top=-1;
do
{
while(p)
{
t[++top]=p;
p=p->left;
}
b=NULL;
while(top>=0)
{
p=t[top];
if(p->right==NULL && p->left==NULL)
{
for(n=0;n<=top;n++)
printf("%3c",t[n]->data);
printf("\n");
top--;
b=p;
}
else if(p->right==b)
{
top--;
b=p;
}
else
{
p=p->right;
break;
}
}
}while(top>=0);*/
//非递归(层次)(逆序输出)
/*BT *q[N],*p;
int front=0,rear=0;
q[rear++]=T;
T->pa=NULL;
while(front!=rear)
{
p=q[front++];
if(p->left)
{
q[rear++]=p->left;
p->left->pa=p;
}
if(p->right)
{
q[rear++]=p->right;
p->right->pa=p;
}
if(p->left==NULL && p->right==NULL)
{
while(p)
{
printf("%3c",p->data);
p=p->pa;
}
printf("\n");
}
}*/
}
void getpa(BT *T)
{
if(T)
{
if(T->left)
T->left->pa=T;
if(T->right)
T->right->pa=T;
getpa(T->left);
getpa(T->right);
}
}
左孩子右兄弟树
typedef struct node
{
char data;
struct node *fir,*sib;
}TR;
插入结点,先根遍历
void insnode(TR *T,char subnode,char newnode);//在树中指定位置插入包含指定数据的节点.
{
TR *q;
if(!T)
return;
else if(T->data==subnode)
{
q=new TR;
q->data=newnode;
q->fir=q->sib=NULL;
if(!T->fir)
T->fir=q;
else
{
T=T->fir;
while(T->sib)
T=T->sib;
T->sib=q;
}
}
else
{
insnode(T->fir,subnode,newnode);
insnode(T->sib,subnode,newnode);
}
}
孩子兄弟的遍历(先序,后序,层次)
void preorder(TR *T)
{
if(T)
{
printf("%3c",T->data);
preorder(T->fir);
Preorder(T->sib);
}
}
void postorder(TR *T)
{
if(T)
{
postorder(T->fir);
postorder(T->sib);
printf("%3c",T->data);
}
}
void levelorder(TR *T)
{
TR *q[N],*p;
int front=0;
int rear=0;
q[rear]=T;
rear++;
while(front!=rear)
{
p=q[front];
front++;
printf("%3c",p->data);
if(p->fir)
{
q[rear]=p->fir;
rear++;
}
if(p->sib)
{
q[rear]=p->sib;
rear++;
}
}
}
图
图的遍历算法(编程只需领接矩阵)
定义结构
typedef sturct arc
{
int index;//定义当前节点
int weight;//定义边的权重
struct arc *next;//指向下一个节点
}AR;
typedef struct mygraph
{
char **vexname;//存储定点名字的动态数组
int vexnum;//图中顶点的个数
int type;//图的种类(1为有向网,0为无向网)
AR *N;//为邻接表的表头定义动态数组
int **A;//邻接矩阵动态数组
}GH;
typedef struct mygraph
{
char **vexnum;//存储点名的动态数组
int vexnum;//图中顶点个数
int type;//图种类
int **A;//邻接矩阵数组
}
BFS
广度优先遍历类似于树的层次遍历,一次BFS的基本过程如下:从起始点v出发,先访问顶点v;然后依次访问v的所有未被访问过的邻接点w1,w2,…,wt;按照w1,w2,…,wt的顺序,再访问它们各自所有未被访问过的邻接点;以此类推,直到图中所有与起始点v连通的顶点都被访问过为止
void BFSvisit(GH G)
{
int i,*visit;//visit是用来做标记的数组,0表示未访问过,1表示访问过,防止重复遍历
visit=new int[G.vexnum];
memset(visit,0,G.vexnum*sizeof(int));//初始,图中的每个定均未被访问,所以初始值全部为0
for(i=0;i<G.vexnum;i++)//对图中每个顶点
{
if(!visit[i])//只要该顶点还未被访问
BFS(G,visit,i);//则以该点为起始点,对图做B广度优先遍历
}
printf("\n");
delete(visit);//释放 visit数组
}
void BFS(GH G,int *visit,int index)
{
//邻接矩阵
int *q,front=0,rear=0;
int i,j;
q=new int[G.vexnum];
q[rear]=index;
rear++;
visit[index]=1;
while(front!=rear)
{
i=q[front];
front++;
printf("%s ",G.vexname[i]);
for(j=0;j<G.vexnum;j++)
{
if(G->A[i][j]&&visit[j]==0)
{
q[rear]=j;
rear++;
visit[j]=1;
}
}
}
delete(q);
}
void BFS(GH G,int *visit,int index)
{
int *q,front=0,rear=0;
int i,j;
q=new int[G.vexnum];
q[rear]=index;
rear++;
visit[index]=1;
while(front!=rear)
{
i=q[front];
front++;
printf("%s",G.vexname[i]);
for(j=0;j<G.vexnum;j++)
{
if(G->A[i][j]&&visit[j]==0)
{
q[rear]=j;
rear++;
visit[j]=1;
}
}
}
delete(q);
}
DFS
深度优先遍历类似于树的先序遍历,一次DFS的过程为:
从起始点v出发,先访问顶点v;选择一个与v的邻接的、且未被访问过的顶点w,作为新的起始点,继续做DFS,直到v的所有邻接点均被访问过
void DFSvisit(GH G)
{
int i,*visit;//这个主调函数与BFSvisit是一致的
visit=new int[G.vexnum];
memset(visit,0,G.vexnum*sizeof(int));//
for(i=0;i<G.vexnum;i++)//
{
if(!visit[i])//只要顶点i还未被访问
DFS(G,visit,i);//以i为起始点对图做DFS遍历
}
printf("\n");//
delete(visit);//
}
void DFS(GH G,int *visit,int index)
{
//邻接矩阵//
int i,j;
printf("%s ",G.vexname[index]);
visit[index]=1;
for(i=0;i<G->vexnum;i++)
{
if(G.A[index][i]&&visit[i]==0)
DFS(G,visit,i);
}
}
Floyd(单源最短路径问题)
Floyd用于解决所有节点之间的最短路径长度问题,同时可以找到每个节点之间的路径
void Floyd(GH G)
{
int i,j,k;//k是中转站
int **dis,**path;//定义两个结构
dis=new int *[G.vexnum];
path=new int *[G.vexnum];
for(i=0;i<G.vexnum;i++)
{
dis[i]=new int[G.vexnum];
path[i]=new int[G.vexnum];
for(j=0;j<G.vexnum;j++)
{
dis[i][j]=G.A[i][j];
if(dis[i][j]<maxx)
path[i][j]=i //从i到j可以走通
else
path[i][j]=-1;//没有路径,i无法到达j;
}
}
//三重循环
//k是中转站,第一重环
//第二重循环,i是起始点
//第三重循环,终止点j循环
for(k=0;k<G.vexnum;k++)
{
for(i=0;i<G.vexnum;i++)
{
for(j=0;j<G.vexnum;j++)
{
if(dis[i][j]>dis[i][k]+dis[k][j])//当前的长度大于以k为终点站的,则进行更新
{
dis[i][j]=is[i][k]+dis[k][j];
path[i][j]=k; //k为中转路径,从i到j经过k
}
}
}
}
}
Floyd(GH G)
{
int i,j,k;
int **path,**dis;
path=new int *[G.vexnum];
dis=new int *[G.vexnum];
for(i=0;i<G.vexnum;i++)
{
dis[i]=new int [G.vexnum];
path[i]=new int[G.vexnum];
for(j=0;j<G,vexnum;j++)
{
dis[i][j]=G.A[i][j];
if(dis[i][j]<maxx)
{
path[i][j]=i;
}
else
path[i][j]=-1;
}
}
//三重循环,k是中转栈
for(k=0;k<G.vexnum;k++)
{
for(i=0;i<G.vexnum;i++)
{
for(j=0;j<G.vexnum;j++)
{
if(dis[i][j]>dis[i][k]+dis[k][j])
{
dis[i][j]=dis[i][k]+dis[k][j];
path[i][j]=k;
}
}
}
}
}
迪杰斯特拉斯算法
首先找最短的路径,将最短的路径存起来,然后找次短路径,然后依次往后。
开辟三个辅助数组:dis,path,flag;
dis[i]=k;//表示从起点抵达顶点i的距离为k;
path[i]=k; // 说明到达顶点i时需要经过顶点k;
flag[i]=1 || 0 ; //1表示已经找到从起点到顶点i的路径了(0表示未找到路径);
普利姆算法
取图中任意一个顶点v作为最小生成树的根,之后往生成树上添加新的顶点w。==在添加的顶点w和已经在生成树上的顶点v之间必定存在一条边,并且该边的权值在所有连通顶点v和w之间的边中取值最小。==之后继续往生成树上添加顶点,直至生成树上含有 n-1 个顶点为止。
取图中任意一个顶点v作为最小生成树的根,之后往生成树上添加新的顶点w。在添加的顶点w和已经在生成树上的顶点v之间必然存在一条边,并且该边的权值在所有连通顶点v与w之间取值最小。之后继续往生成树上添加顶点,直到生成树上含有n-1个顶点为止。
用vexcode记录在U中的顶点,另一个V-U中的顶点号用数组的位置序号表示。 lowcost存储最短距离
克鲁斯卡尔算法
使生成树中每一条边的权值尽可能地小;
将所有的边按照权值升序排序,然后按顺序,只要当前所选边不产生回路时,就把它选定;如果无向网中包含n个顶点,那么只需要选定n-1条边即可。
拓扑排序与关键路径
1.确定拓扑排序
2.确定ve(最大的),vl(最小的,作差的值最大的)
3.确定关键节点以及关键路径(关键路径不一定唯一)
查找
ASL都要会算
顺序查找
1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|
12 | 34 | 5 | 75 | 22 | 54 | 33 |
计算ASL:
A S L = ( 1 + 2 + ⋯ + 7 ) 7 = n + 1 2 ASL=\frac{(1+2+\cdots+7)}{7}=\frac{n+1}{2} ASL=7(1+2+⋯+7)=2n+1
折半查找(会写)
int BinarySearch( Stable R, KeyType key )
{
int low=1, high=R.length, mid; // 设置初始的查找区间
while( low<=high ) // 只要查找区间存在
{
mid = (low+high)/2; // 计算查找区间的中间位置
if( R.SeqList[mid].key==key ) // 查找成功
return mid;
else if( R.SeqList[mid].key<key )
low = mid+1; //继续在后半区查找
else high = mid-1; //继续在前半区查找
} // end while
return 0; // 查找不成功
} // BinarySearch
int binsort(stable R,int key)
{
int low=1,high=R.length,mid;
while(low<=high)
{
mid=(high+low)/2;
if(R.elem[mid].key==key) return mid;
else if(R.elem[mid].key<key) low=mid+1;
else high=mid-1;
}
return 0;
}
int bysort(stable R,int key)
{
int low=1,high=R.length,mid;
while(low<=high)
{
mid=(low+high)/2;
if(R.elem[mid].key==key)
return mid;
else if(R.elem[mid]
}
}
计算折半查找的ASL
void by_asl(int low,int high,int lay,int *comp)
{
int mid;
if(low<=high)
{
mid=(low+high)/2;
*comp=*comp+lay;
by_asl(low,mid-1,lay+1,comp);
by_asl(mid+1,high,lay+1,comp);
}
}
float asl(int n)
{
int comp=0;
by_asl(1,n,1,&comp);
return comp*1.0/n;
}
BST
树表(BST会写代码,在BST的查找代码,用递归和非递归都可以,然后BST创建以及怎么插入一个结点代码,BST要会遍历,中序BST是有序的)
typedef struct node
{
int data;
node *left,*right;
}BST;
BST的查找代码
/*
若二叉排序树为空,则查找不成功,返回空;
否则:
若定值等于根节点的关键字,则查找成功;
若定值小于根节点的关键字,则从左子树上查找
若定值大于根节点的关键字,则从右子树上查找
*/
BST *findnode(BST *T,int data)
{
//递归
if(!T)
return NULL;
else if(T->data==data)
return T;
else if(T->data<data)
return findnode(T->right,data);
else
return findnode(T->left,data);
}
BST *findnode(BST *T,int data)
{
if(!T) return NULL;
else if(T->data==data) return T;
else if(T->data<data) return findnode(T->right,data);
else return findnode(T->left,data);
}
//查找一棵树的data,先判断树是否为空,若为空则返回空;若不为空,则若为data,则返回T;
//否则,若插入关键字大,则返回右子树;否则返回左子树
BST *findnode(BST *T,int data)
{
if(!T) return NULL;
else if(T->data==data) return T;
else if (T->data<data) return findnode(T->right,data);
else return find(T->left,data);
}
BST创建以及怎么插入一个结点
/*
根据定义,“插入”操作是在查找不成功时才进行的;
若二叉排序树为空树,则新插入的结点为新的根节点,否则:
若待插入结点的关键字小于根节点的关键字,则从左分支插入
否则从右分支插入
注:新插入结点必为叶子结点
*/
BST *insnode(BST *T,int data)
{
//递归
if(!T)
{
T=new BST;
T->data=data;
T->left=T->right=NULL;
return T;
}
else if(T->data==data)
return T;
else if(T->data<data)
T->right=insnode(T->right,data);
else
T->left=insnode(T->left,data);
return T;
}
BST *insnode(BST *T,int data)
{
if(!T)
{
T=new BST;
T->data=data;
T->left=NULL;
T->right=NULL;
return T
}
else if(T->data==data)
return T;
else if(T->data<data)
T->right=insnode(T->right,data);
else
T-left=insnode(T->left,data);
return T;
}
//插入是在查找成功之后进行的
//如果没有树,则需要先创建一棵树,再将其插入
//如果要插入的关键字大于当前节点的关键字,则需要从当前节点的右子树插入;否则从左子树插入
int *insnode(BST *T,int data)
{
if(!T)
{
T=new BST;
T->data=data;
T->left=NULL;
T->right=NULL;
return T;
}
else if(T->data==data)
return T;
else if(T->data<data)
T->right=insnode(T->right,data);
else
T->left=insnode(T->left,data);
return T;
}
//若要插入一个节点,且如果是空树,则需要先创建一个空树,然后将data赋给它;
//否则若关键字大于当前节点的关键字,则需要访问右子树
//否则访问左子树
//注意return,插入的必定是叶子结点
int *insnode(BST *T,int data)
{
if(!T)
{
T=new BST;
T->data=data;
T->left=NULL;
T->right=NULL;
return T;
}
else if(T->data==data)
return T;
else if(T->data>data)
T-Left=insnode(T->left.data);
else T->right=insnode(T->right,data);
return T;
}
B-树
Note
注意,做删除的时候是比它小的分支里面找一个极大值,或者在比它大的分支里面找一个极小值对它来做替换,然后在相应位置删掉。
请根据关键字序列{23 12 9 26 36 17 14 32 15 30}创建3阶的B-树,要求 1、画出创建后的B-树示意图;2、若依次删除关键字17、9和12,请画出每删除一个关键字后的B-树(如果删除的关键字不在最底层,那么用其右分支中的极小值来做替换后,删除该极小值);
AVL
1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|
y | B | z | C | w | A | x |
哈希函数应用题
处理冲突的方式
当出现冲突时,有三种解决方法:
(1)线性探测再散列
d i = c × i d_i=c \times i di=c×i, 最简单的情况 c = 1 c=1 c=1
关键字 {21,19,54,62,7,32,29,44,15},哈希函数 H(key) = key MOD 11
H(21)=21 %11=10
H(19)=19%11=8
H(54)=54%11=10,H2(54)=(10+1)%11=0
H(62)=62%11=7
H(7)=7%11=7,H2(7)=(7+1)%11=8,H3(7)=(7+2*1)%11=9
H(32)=32%11=10,H2(32)=11%11=0,H3(32)=12%11=1
H(29)=29%11=7,H2(29)=8,H3(29)=9,H4(29)=10,H5(29)=0,H6(29)=1,H7(29)=2
H(44)=44%11=0,H2(44)=1,H3(44)=2,H4(44)=3
H(15)=15%11=4
ASL=(4+2+3+3+7+4)/9=23/9
(2)平方探测再散列
d i = 1 2 , − 1 2 , 2 2 , − 2 2 , ⋯ , d_i = 1^2, -1^2, 2^2, -2^2, \cdots, di=12,−12,22,−22,⋯,
H(21)=21%11=10
H(19)=19%11=8
H(54)=54%11=10,H2(54)=(10+1)%11=0
H(62)=62%11=7
H(7)=7%11=7,H2(7)=8,H3(7)=(7-1)%11=6
H(32)=32%11=10,H2(32)=(10+1)%11=0,H3(32)=(10-1)%11=9
H(29)=29%11=7,H2(29)=8,H3(29)=6,H4(29)=(7+4)%11=0,H5(29)=(7-4)%11=3
H(44)=44%11=0,H2(44)=1
H(15)=15%11=4
ASL=(4+2+3+3+5+2)/9=19/9
(3) 随机探测再散列
$d_i 是一组伪随机数列或者 是一组伪随机数列 或者 是一组伪随机数列或者d_i=i×H_2(key) $(又称双散列函数探测)
例如: 关键字 {21,19,54,62,7,32,29,44,15}
设定哈希函数 H(key) = key MOD 11,采用双哈希 H2(key)=(3 key) MOD 10+1
H(21)=21%11=10
H(19)=19%11=8
H(54)=54%11=10,冲突: H2(54)=(3*54)%10+1=3 则:H(54)=(10+1×3)%11=2
H(62)=62%11=7
H(7)=7%11=7, 冲突:H2(7)=(21)%10+1=2,H(7)=(7+2)%11=9
H(32)=32%11=10,冲突:H2(32)=(96)%10+1=7,则:H(32)=(10+1×7)%11=6
H(29)=29%11=7,冲突,H2(29)=(29*3)%10+1=8,H(29)=(7+8)%11=4
H(44)=44%11=0
H(15)=15%11=4,冲突,H2(15)=(45)%10+1=6,H(15)=(4+6)%11=10,H(15)=(4+12)%11=5
ASL=(4+2+2+2+2+3)/9=15/9
排序
冒泡排序
void sortdata(int *a,int n)//冒泡排序(数组)
{
int i,j,temp;
for(i=0;i<n-1;i++)
{
for(j=0;j<n-i-1;j++)
{
if(a[j]>a[j+1])
{
temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
}
}
}
//冒泡排序,每次让判断当第i个大于第i+1个时,两个元素之间进行一次交换
void bubblesort(int *a,int n)
{
int i,j,t;
for(i=0;i<n-1;i++)
{
for(j=0;j<n-i-1;j++)
{
if(a[j]>a[j+1])
{
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
}
}
typedef struct node
{
int data;
struct node *next;
}LI;
void bubble(LI *L)
{
int count=0;
LI *p,*q,*tail
p=L;
while(p->next!=NULL)
{
count++;
p=p->next;
}
for(int i=0;i<count-1;i++)
{
q=L->next;
p=q->next;
tail=L;
int j=count-i-1;
while(j--)
{
if(q->data>p->data)
{
q->next=p->next;
p->next=q;
tail->next=p;
}
}
p=p->next;
q=q->next;
tail=tail->next;
}
}
一次快排的分割会写
基本思想:通过一趟快排过程,将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,再分别对这两部分记录继续递归进行排序,最终达到整个序列有序
一趟快排的目标:找一个记录,以它的关键字作为“枢轴”,凡其关键字小于等于枢轴的记录均移动至该记录之前,反之,凡关键字大于等于枢轴的记录均移动至该记录 之后
int pivot(int *a,int low,int high)
{
a[0]=a[low];
while(low<high)
{
while(low<high&&a[high]>=a[0])
{
high--;
}
a[low]=a[high];
while(low<high&&a[low]<=a[0])
{
low++;
}
a[high]=a[low];
}
a[low]=a[0];
return low;
}
int pivot(int *a,int low,int high)
{
a[0]=a[low];
while(low<high)
{
while(low<high&&a[low]<=a[0])
{
low++;
}
a[high]=a[low];
while(low<high&&a[high]>=a[0])
{
high--;
}
a[low]=a[high];
}
a[low]=a[0];
return low;
}
int pivot(int* a, int low, int high)
{
int i = low; // i 指向第一部分的末尾(余数为1)
int j = high; // j 指向第二部分的开头(余数为2)
// 第一轮:将余数为2的元素移到数组的末尾
while (i < j)
{
while (i < j && a[j] % 3 == 2)
{
j--;
}
a[i]=a[j];
while (i < j && a[i] % 3 != 2)
{
i++;
}
a[j]=a[i];
}
// 此时,i 指向第一个余数为2的元素的位置
int mid = i; // 保存第一个余数为2的元素位置
i = low;
j = mid - 1;
// 第二轮:在前半部分中,将余数为1的 元素移到前面
while (i < j)
{
while (i < j && a[j] % 2 == 0)
{
j--;
}
a[i]=a[j];
while (i < j && a[i] % 2 == 1)
{
i++;
}
a[j]=a[i];
}
// 返回第一个余数为2的元素的位置
return mid;
}
int pivot(int *a,int low,int high)
{
int i=low,j=high;
while(i<j)
{
while(i<j&&a[j]%3==2)
{
j--;
}
a[i]=a[j];
while(i<j&&a[i]%3!=2)
{
i++;
}
a[j]=a[i];
}
int mid=i;//mid指向第一个余数为2的元素
i=low;
j=mid-1;
while(i<j)
{
while(i<j&&a[j]%2==0)
{
j--;
}
a[i]=a[j];
while(i<j&&a[i]%2!=0)
{
i++;
}
a[j]=a[i];
}
return mid;
}
int pivot(int *a,int low,int high)
{
int i=low;
int j=high;
while(i<j)
{
while(i<j&&a[j]%3==2)
{
j--;
}
a[i]=a[j];
while(i<j&&a[i]%3!=2)
{
i++;
}
a[j]=a[i];
}
int mid=i;//mid为第二段第一个
i=low;
j=mid-1;
while(i<j)
{
while(i<j&&a[j]%2==0)
{
j--;
}
a[i]=a[j];
while(i<j&&a[i]%2==1)
{
i++;
}
a[j]=a[i];
}
return mid;
}
a数组分成三部分,第一部分是除以2余数为1的,第二部分是余数为0的,第三部分是除以2余数为2的;方法还是上述的快排,进行两轮快排。第一轮:low所指向的数据的余数小于等于1,则low向后移动,当不满足时则停下来;high所指向的余数为2,则向前移动,当不满足时则停下来,再交换,这样最终前段均为余数为0或1的,后半段均为余数为2的;第二轮对前半段做处理,将余数为1的放到前半段,将余数为0的放到后半段.
堆排
从上到下筛选的那个要会写,A[i]=e,确保它依然是大顶堆,做筛选,优先队列,大顶堆,先向上判断,再向下判断,跟较大的孩子替换,哨兵存给,然后孩子向上移动;如果它比父亲大,则向上筛选,父亲向下.
算法思想
以大顶堆为例(堆顶即为最大值):
首先将堆顶与当前最后一个元素交换,最大者就正好存储在正序序列的最终位置上再将剩余n-1个元素的序列重新调整成一个大顶堆;
重复上述步骤,便会得到一个升序有序的序列;
关键问题如何创建大(小)顶堆如何在输出堆顶元素之后,快速调整剩余元素成为一个新的大(小)顶堆.
堆排序过程
(1)通过筛选,将待排数据调整为数据规模为N的大(小)顶堆;
(2)将堆顶与堆的最后一个元素交换,堆的规模减一;
(3)对堆顶元素做筛选;
(4)重复2-3步,直至堆中仅剩余一个元素为止。
void heapsort(int* a, int n)
{
int i, j, * b;
b = new int[n + 1];
memcpy(b, a, (n + 1) * sizeof(int));
for (i = n / 2; i >= 1; i--)
heapadjust(b, n, i);
for (i = 1; i < n; i++)//排序
{
b[0] = b[1];
b[1] = b[n - i + 1];
b[n - i + 1] = b[0];
heapadjust(b, n - i, 1);
}
delete(b);
}
基本代码
void heapadjust(int* a, int n, int index)
{
int i = index, j = 2 * i;
a[0] = a[i];// 保存当前要调整的节点的值
while (j <= n)//下滤
{
if (j + 1 <= n && a[j] < a[j + 1])//子节点中值较大
j++; //索引本来是左孩子,现指向右孩子
if (a[j] > a[0])//若子节点的值大于父节点的值,上滤
{
a[i] = a[j];
i = j;
j = 2 * i;
}
else
break;
}
a[i] = a[0]; //将排好的结点放入最终位置
}
//做堆排序,首先令i=index,j=2*i,然后将a[0]=a[i]存储,然后下滤,让j+1<=n时判断a[j]与a[j+1]之间的大小关系,然后若a[j]比a[0]大,则将a[i]=a[j],i=j,j=2*i。每次循环到j>n时,即没有孩子时退出算法,将a[i]=a[0]
void headpad(int *a,int n,int index)
{
int i=index;
int j=2*i;
a[0]=a[i];
while(j<=n)
{
if(j+1<=n&&a[j]<a[j+1])
j++;
if(a[0]<a[j])
{
a[i]=a[j];
i=j;
j=2*i;
}
else
break;
}
a[i]=a[0];
}
A[i]=e
int headjust(int *a,int n,int i,int e)
{
a[i]=e;
int parent=i/2;
int child;
a[0]=a[i];
//上滤
while(i>1&&a[parent]<a[0])
{
a[i]=a[parent];
i=parent;
parent=i/2;
}
//下滤
while(2*i<=n)
{
child=2*i;
if(child+1<=n&&a[child]<a[child+1])
child++;
else if(a[0]<a[child])
{
a[i]=child;
i=child;
child=2*i;
}
else
break;
}
a[i]=a[0];
}
堆排序在最坏的情况下,其时间复杂度也为O(NlogN) ,相对于快速排序来说,这是堆排序的最大优点
归并算法
-
整体归并排序的时间复杂度是O(NlogN);
-
辅助数组做异地归并,因此空间复杂度是O(N);(空间复杂度最大)
-
总是相邻的元素做比较和移动,因此是稳定的
基数排序
分配和收集意味着数据的移动,为了方便实现,基数排序一般使用链式结构
- “分配” 时,按当前“关键字位”所取值,将记录分配到不同的 “链队列” 中,每个队列中记录的 “关键字位” 相同
- “收集”时,按当前关键字位取值从小到大将各队列首尾相链成一个链表
分析
- 基数排序是对每个记录按位进行“分配”和“收集”的,每一次的分配和收集的时间复杂度是O(N);
- 基数排序整体的时间复杂度是O(d*N),d表示分配和采集的次数。
- 采用队列进行“分配”和“收集”,队列的先进先出特性,保持了相同关键字记录的先后次序,算法是属于稳定的排序方法
- 空间复杂度是O©
算法比较
空间性能
- 所有的简单排序算法以及希尔排序、堆排、基数排序都是本地实现,空间复杂度O©;
- 快排要通过递归实现,即使不用递归,也需要使用栈,栈的深度为O(logN);
- 归并需要一个与待排数据规模一样的辅助数组做异地归并,空间复杂度最高,O(N)
稳定性
- 只有直接插入、冒泡、归并以及基数排序是稳定的,其余的皆不稳定