数据结构题集

本文介绍了数据结构中的核心概念,包括时间复杂度和空间复杂度的计算,线性表的操作,栈和队列的实现,字符串处理的KMP算法,以及二叉树和赫夫曼树的相关问题。同时,给出了多个实际的算法实现和考研真题示例,如查找三元组最小距离,线性表的重新排列,统计未出现的最小正整数等。
摘要由CSDN通过智能技术生成

一、时间复杂度和空间复杂度

算法效率分析分为两种:第一种是时间效率,第二种是空间效率。
时间效率被称为时间复杂度,而空间效率被称作空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间

时间复杂度的计算

时间复杂度是一个衡量算法时间的相对标准。他不是一个固定的时间例如多少分多少秒,而是一个大概的估计数值。比如同一个算法在不同的机器上运行的时间肯定不同。这时候就需要我们的时间复杂度来衡量算法的时间效率

时间复杂度通常使用大O的渐进表示法(空间复杂度也是)

列举几个实例给搭建看看 O(N),O(N^2),O(1)
如上所示就是大O的渐进表示法。括号内就是算法执行的次数,要记住时间复杂度计算的是执行次数
但是他不是一个准确的数字而是一个估计值,比如(N^2+2N+10)在N很大的时候除了最高次项其他的都可以忽略不计,这类似与数学中的极限思想,只保留对结果影响最大的哪一项。

推导法则如下:
1.若括号内是常数都写成O(1)
2.若是括号内是多项式例如O(N^2+5n+2)则只保留最高次项
3.若是最高次项的系数不为1则统一写成1

下面我们来看几个例子学习一下:

// 请计算一下Func1基本操作执行了多少次?
void Func1(int N)
{
int count = 0;
for (int i = 0; i < N ; ++ i)
{
for (int j = 0; j < N ; ++ j)
{
		++count;
}
}
for (int k = 0; k < 2 * N ; ++ k)
{
		++count;
}
int M = 10;
while (M--)
{
		++count;
}
		printf("%d\n", count);
}

这个算法的具体执行次数是(N*N+2N+10)而他的时间复杂度是O(N^2)

// 计算strchr的时间复杂度?
const char * strchr ( const char * str, char character )
{
while(*str != '\0')
{
if(*str == character)
		return str;
		++str;
}
		return NULL;
}

要注意有的算法有最坏情况,最好情况和平均情况。比如上面这个算法是在一个字符串中寻找一个字符,最好情况就是一上来就找到了,时间复杂度是O(1),最坏情况是遍历完了字符串才找到或者没找到时间复杂度为O(N),平均情况为O(N/2);

二、线性表

1.输出倒数第k个结点请添加图片描述

从头到尾遍历单链表,并用指针q指向当前结点的前k个结点,当遍历到链表尾部时,q所指的结点即为倒数第k个结点

int findNode(LNode *head, int k)
{
	LNode *p = head->next, *q = head;
	int i = 1;
	while(p != NULL)
	{
		p = p->next;
		i++;
		
		if(i > k)
		{
			q = q->next;
		}
	}
	if (q == head)  //没有k个结点
	    return 0;
	else
	{
		cout << q->data << endl;
		return 1;
	} 
}

三、栈和队列

1.用仅有尾指针的循环单链表实现队列

//入队
void EnQueue(LNode *&rear, int x)
{
    LNode *s = (LNode*)malloc(sizeof(LNode);
    s->data = x;
    s->next = rear->next;
    rear->next = s;
    rear = s;
}

//出队
int DeQueue(LNode *&rear, int &x)
{
    LNode *s;
    if(rear->next == rear)
        return 0;
    else
    {
        s = rear->next->next;  //s指向开始结点
        rear->next->next = s->next;  //队头元素出栈
        x = s->data;
        if(s == rear)  //若出队后为空
            rear = rear->next;
        free(s);
        return 1;
    }
}

三、串

1.KMP算法

void getnext(Str substr, int next[])
{
    int i = 1, j = 0;
    next[i] = j;
    while(i < substr.length)
    {
        if(j == 0 || substr.ch[i] == substr.ch[j])
        {
            i++;
            j++;
            next[i] = j;
        }
        else
            j = next[j];
    }
}

int KMP(Str str, Str substr, int next[])
{
    int i = 1, j = 0;
    while(i <= str.length && j <= substr.length)
    {
        if(j == 0 || str.ch[i] == substr.ch[j])
        {
            i++;
            j++;
        }
        else
            j = next[j];
    }
    if(j > substr.length)
        return i - substr.length;
    else
        return 0;
}

//改进
void getnextval(Str substr, int nextval[])
{
    int i = 1, j = 0;
    nextval[i] = j;
    while(i < substr.length)
    {
        if(j == 0 || substr.ch[i] == substr.ch[j])
        {
            i++;
            j++;
            if(substr.ch[i] != substr.ch[j])
                nextval[i] = j;
            else
                nextval[i] = nextval[j];
        }
        else
            j = nextval[j];
    }
}

请添加图片描述

五、树与二叉树

  • 树的先序遍历对应于二叉树的先序遍历,树的后序遍历对应于二叉树的中序遍历
  • 具有n个结点的完全二叉树的深度为⌊log2n⌋+1
  • 用n个值组成一个赫夫曼树,会有2n-1个结点(n + n - 1)
  • n个结点的线索二叉树有n-1个线索
  • 度为m的赫夫曼树中,叶子结点数为n,则非叶子结点数为:

叶结点即度为0的结点有n个;假设度为m的结点个数为x,则x+n=mx+1;也就是x=n-1/m-1;
若n-1不能被整除,即所给数据不能直接构造最优m叉树,这时需要加一些不影响建树的数据,可以添0;添加的个数为(m-1)-((n-1)%(m-1))。所以最终x应该为⌈n-1/m-1⌉ ,即向上取整;

请添加图片描述
请添加图片描述
打印根节点到叶子结点的路径
在这里插入图片描述

char pathStack[maxSize];  //路径栈
void printPath(BTNode *p)
{
    int i, top = -1;  //top为栈顶指针
    if(p == NULL)  //空树
        return;
    pathStack[++top] = p->fata;  //上图(1)处,p是从上往下走,结点入栈
    if(p->lchild == NULL && p->rchild == NULL)  //叶子结点
    {
        //打印路径
        for(i = 0; i < top; i++)
            cout << pathStack[i] << "->";
    }
    printPath(p->lchild);
    printPath(p->rchild);
    top--;  //上图(3)处,p是从下往上走,结点出栈

考研真题

1.2020求三元组的最小距离

定义三元组(a,b,c)(a,b,c均为整数)的距离D =∣a−b∣+∣b−c∣+∣c−a∣。
给定3个非空整数集合S1,S2和S3,按升序分别存储在3个数组中。
请设计一种尽可能高效的算法,计算并输出所有可能的三元组(a,b,c)(a∈S1,b∈S2,c∈S3)中的最小距离
例如,S1={-1, 0, 9},S2={-25, -10, 10, 11},S3={2, 9, 17, 30, 41},
则最小距离为2,相应的三元组为(9, 10, 9)。

请添加图片描述

int isMin(int x, int y, int z){
	if(x <= y && x <= z)
		return 1;
	else 
		return 0;
}
int main(){
	int S1[] = {-1,0,9};
	int S2[] = {-25, -10, 10, 11};
	int S3[] = {2, 9, 17, 30, 41};
	int res[3] = {0, 0, 0};
	int Length1 = sizeof(S1)/sizeof(S1[0]);
	int Length2 = sizeof(S2)/sizeof(S2[0]);
	int Length3 = sizeof(S3)/sizeof(S3[0]);
 
	int D = INT_MAX; 
	int i = 0, j = 0, k = 0; 
	while(i < Length1 && j < Length2 && k < Length3 && D > 0){
		int dis = abs(S1[i] - S2[j]) + abs(S2[j]- S3[k]) + abs(S3[k] - S1[i]);
		if(dis < D){
			 D = dis;
			 res[0] = S1[i];
			 res[1] = S2[j];
			 res[2] = S3[k];
		}
		if(isMin(S1[i],S2[j],S3[k])) i++;
		else if(isMin(S2[j],S3[k],S1[i])) j++;
		else k++;
	}
	cout << res[0] << " " << res[1] << " " << res[2] << " " << endl;
	return 0;
}

2.2019线性表的重新排列

请添加图片描述

1.用两个指针交替进行,找到链表的中间结点
2.将单链表的后半段逆置
3.从单链表的前后各取一个结点,按要求依次重排

void change_list(NODE* head)
{
     NODE * mid,*front, *tail, *temp;
     mid = temp = head;
     //寻找中间结点
     while (temp->next != NULL)
     {
         mid = mid->next;   //mid走一步
         temp = temp->next;     //temp走两步
         if (temp->next != NULL)
             temp = temp->next;
     }
     tail = mid->next;  //tail为后半不分链表的首结点
     mid->next = NULL;
     //后半段逆置
     while (tail != NULL)
     {
         temp = tail->next;
         tail->next = mid->next;
         mid->next = tail;
         tail = temp;
     }
     front = head->next;  //前半段的第一个数据结点,即插入点
     tail = mid->next;  //后半段的第一个数据结点
     //开始重组链表
     mid->next = NULL;
     while (tail != NULL)
     {
         temp = tail->next;
         tail->next = front->next;
         front->next = tail;
         front = tail->next;
         tail = temp;
     }
}    

3.2018统计未出现的最小正整数

请添加图片描述

int findMissMin(int A[], int n)
{
    int i, * B; //标记数组
    B = (int*)malloc(sizeof(int) * n);//分配空间
    memset(B, 0, sizeof(int) * n); //赋初值为0
    for (i = 0; i < n; i++)
        if (A[i] > 0 && A[i] <= n) //若A[i]的值介于1~n,则标记数组B
            B[A[i] - 1] = 1;
    for (i = 0; i < n; i++) //扫描数组B,找到目标值
        if (B[i] == 0) break;
    return i + 1; //返回结果
}

4.2017表达式树转为中缀表达式

请添加图片描述

void BtreeToE(BTree *root){
    BtreeToExp(root, 1); //根的高度为 1 
}
void BtreeToExp( BTree *root, int deep)
{
    if(root == NULL) return; //空结点返回
    else if(root->left==NULL&&root->right==NULL) //若为叶结点
        printf(%s”, root->data); //输出操作数,不加括号
    else{
        if(deep>l) 
            printf((); //若有子表达式则加1层括号
        BtreeToExp(root->left, deep+1);
        printf(%s”, root->data); //输出操作符
        BtreeToExp(root->right, deep+1);
        if(deep>l) 
            printf()); //若有子表达式则加1层括号
    } 
}

5.2015删除链表的绝对值相等的元素

请添加图片描述

typedef struct node {
    int data;
    struct node* link;
}NODE;
typedef NODE* PNODE;

void func(PNODE h, int n)
{
    PNODE p = h, r;
    int* q, m;
    q = (int*)malloc(sizeof(int) * (n + 1)); //申请n+1个位置的辅助空间
    for (int i = 0; i < n + 1; i++) //数组元素初值置0
        *(q + i) = 0;
    while (p->link != NULL)
    {
        m = p->link->data > 0 ? p->link->data : -p->link->data;
        if (*(q + m) == 0) //判断该结点的data是否已出现过
        {
            *(q + m) = 1; //首次出现
            p = p->link; //保留
        }
        else //重复出现
        {
            r = p->link; //删除
            p->link = r->link;
                free(r);
        }
    }
    free(q);
}

6.2014求带权路径长度(WPL)

请添加图片描述

typedef struct BiTNode {
    int weight;
    struct BiTNode* lchild, * rchild;
}BiTNode, * BiTree;
int WPL(BiTree root) {
    return wpl_PreOrder(root, 0);
}
int wpl_PreOrder(BiTree root, int deep) {
    static int wpl = 0; //定义一个 static 变量存储 wpl
    if (root->lchild == NULL && root->rchild == NULL)  //若为叶子结点,累积 wpl
        wpl += deep * root->weight;
    if (root->lchild != NULL) //若左子树不空,对左子树递归遍历
        wpl_PreOrder(root->lchild, deep + 1);
    if (root->rchild != NULL) //若右子树不空,对右子树递归遍历
        wpl_PreOrder(root->rchild, deep + 1);
    return wpl;
}

7.2013找主元素

请添加图片描述

排序后的中位数

int majority(int A[], int n)
{
	//排序 
	sort(A,A + n);
	
	int num = 0, temp;
	if(n % 2 == 0)
	    temp = A[n / 2];
	else
	    temp = A[n / 2 + 1];
	
	for(int i = 0; i < n; i++)
	{
		if(A[i] == temp)
		    num++;
	}
	if(num > n / 2)
	    return temp;
	else
	    return -1;
	
	return 0;
}

8.2011两个升序序列的中位数

请添加图片描述

算法的基本设计思想如下:
分别求出序列 A 和 B 的中位数,设为 a 和 b,求序列 A 和 B 的中位数过程如下:
① 若 a=b,则 a 或 b 即为所求中位数,算法结束。
② 若 a<b,则舍弃序列 A 中较小的一半,同时舍弃序列 B 中较大的一半,要求舍弃的长度
相等。
③ 若 a>b,则舍弃序列 A 中较大的一半,同时舍弃序列 B 中较小的一半,要求舍弃的长度
相等。
在保留的两个升序序列中,重复过程①、②、③,直到两个序列中只含一个元素时为止,较小者即为所求的中位数。

int M_Search(int A[], int B[], int n) {
    int s1 = 0, d1 = n - 1, m1, s2 = 1, d2 = n - 1, m2;
    //分别表示序列 A 和 B 的首位数、末位数和中位数
    while (s1 != d1 || s2 != d2) {
        m1 = (s1 + d1) / 2;
        m2 = (s2 + d2) / 2;
        if (A[m1] == B[m2])
            return A[m1]; //满足条件 1)
        if (A[m1] < B[m2]) { //满足条件 2)
            if ((s1 + d1) % 2 == 0) { //若元素个数为奇数
                s1 = m1; //舍弃 A 中间点以前的部分,且保留中间点
                d2 = m2; //舍弃 B 中间点以后的部分,且保留中间点
            }
            else { //元素个数为偶数
                s1 = m1 + 1; //舍弃 A 中间点及中间点以前部分
                d2 = m2; //舍弃 B 中间点以后部分且保留中间点
            }
        }
        else { //满足条件 3)
            if ((s1 + d1) % 2 == 0) { //若元素个数为奇数
                d1 = m1; //舍弃 A 中间点以后的部分,且保留中间点
                s2 = m2; //舍弃 B 中间点以前的部分,且保留中间点
            }
            else { //元素个数为偶数
                d1 = m1 + 1; //舍弃 A 中间点以后部分,且保留中间点
                s2 = m2; //舍弃 B 中间点及中间点以前部分
            }
        }
    }
    return A[s1] < B[s2] ? A[s1] : B[s2];
}

算法的时间复杂度为 O(log2n),空间复杂度为 O(1)

9.2010数组左移

请添加图片描述

将R中前P个元素逆置,再将其余的元素逆置,最后整体逆置

void reverse(SqList &L)
{
	int i,j;
	int temp;
	for(i = 0, j = L.length; i < j ; ++i, --j)
	{
		temp = L.data[i];
		L.data[i] = L.data[j];
		L.data[j] = temp;
	}
}

void RCR(int R[], int n, int P)
{
	if(p <= 0 || p >= n)
	    return;
	else
	{
		Reverse(R, 0, P - 1);
		Reverse(R, P, n - 1);
		Reverse(R, 0, n - 1);
	}
} 

习题

1.顺序表删除元素

已知长度为n的线性表L采用顺序存储结构,编写一个时间复杂度为O(n),空间复杂度为O(1)的算法,该算法删除线性表中所有值为x的数据元素

void  Delnode(sqList &L, ElemType x)
{    int  k=0, i;
     for(int i=0; i<L.Length(); i++)
       if(L.data[i]!=x)
	      {
	          L.data[k]=L.data[i]; 
	          k++;
	      }
}


2.链表的就地逆置

就地:辅助空间为O(1)

void Reversel(LinkList*& L)
{
    LinkLst* p = L->link, *q;
    L->link = NULL;
    while(p != NULL)
    {
        q = p->link;  //保存当前节点的下一个节点
        p->link = L->link;  //更新当前节点的指针域
        L->link = p;  //更新当前节点上一个节点的位置
        p = q;  //更新当前节点的位置
    }
}

3.表达式匹配

设置一个栈,扫描表达式exp,遇到’(’,’[’,’{’时,将其入栈;遇到’)’,’]’,’}’时,将栈顶符号弹出,与之进行匹配。如果匹配,算法继续进行;否则 表达式的括号不匹配。当算法终止时,栈不为空,exp的括号不匹配;否则,exp成功匹配。

bool Match(char exp[], int n)
{
    char st[maxSize];
    int i, top = -1;  //栈顶指针
    bool tag = true;
    while(i < n && tag)
    {
        if(exp[i] == '(' || exp[i] == '[' || exp[i] == '{')
        st[++top] = exp[i];
        else if(exp == ')')
            if(st[top] == '(')
                top--;
            else
                return false;
        else if(exp == ']')
            if(st[top] == '[')
                top--;
            else
                return false;
        else if(exp == '}')
            if(st[top] == '{')
                top--;
            else
                return false;
    }
    if(top >= 0)
        return false;
    else
        return true;
}

4.只有尾指针的循环队列

//入队
void EnQu(Qnode*& rear, ElemType x)
{
    Qnode* s = new Qnode;
    s->data = x;
    if(rear == NULL)
    {
        s->next = s;
        rear = s;
    }
    else
    {
        rear->next = s;
        rear = s;
    }
}
//出队
int DeQu(Qnode*& rear, ElemType& x)
{
    Qnode* q;
    if(rear == NULL)
        return 0;
    else if (rear->next == rear)
    {
        x = rear->data;
        delete rear;
        rear = NULL;
    }
    else
    {
        q = rear->next;
        x = q->data;
        rear->next = q->next;
        delete q;
    }
    return 1;
}

5.求所有简单路径

从顶点m出发遍历,若找到了顶点v,则输出path;否则找到m的相邻点w,若顶点没有访问,则从w开始递归遍历。

void PathAll1(Graph *G,int u,int m,int v,int path[]int d)
{
    ArcNode *p;
    int j, w;
    if(m == v) 
    { 
         for(j = 0; j <= d; j++) 
             cout << path[j] << ' '; 
         cout << endl;;
     }
     else
     {
         p=G->NodeTable[m].adj;
         while(p!=NULL)
         {
             w=p->dest;
             if(visited[w]==0)
             {
                 visited[w]=1;
                 path[d+1]=w;
                 Path(G, u, w, v, path, d+1);
                 visited[w]=0;
             }
             p=p->link;
         }  
     }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值