南京航空航天大学-数学学院-数据结构期末考试复习资料(2024年版)

链表

链表插入



//                              有序链表中做插入
//
//   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=a1n+n(n1)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用于存储当前每一列中第一个非零元素所在的位置。

观察上图,容易得到:

12345
cnum12212

接下来,有:cpot[1]=1;

此外,有:cpot[i]=cpot[i-1]+cnum[i-1];

计算之后得到:

12345
cnum12212
cpot12467
  • 对源三元组进行遍历,每一次读取其列号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都要会算

顺序查找

1234567
1234575225433

计算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

1234567
yBzCwAx

哈希函数应用题

处理冲突的方式

当出现冲突时,有三种解决方法:

(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)
稳定性
  • 只有直接插入、冒泡、归并以及基数排序是稳定的,其余的皆不稳定
  • 28
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值