数据结构之串、数组和广义表的相关实现(C语言)

参考:1.网课:数据结构与算法基础(青岛大学-王卓)
2.教材:数据结构(c语言版)第二版,严蔚敏,李冬梅等著

非科班自学,如有错误望不吝赐教。

  • 串可以理解成数据对象为字符集的线性表
  • 定长顺序串包括一个确定长度的数组,和一个整型变量显示当前串中字符的个数 ;链式串包含串的头,尾指针和结点个数
  • 串的基本操作重点关注查找匹配,书上介绍了BF算法和KMP算法

0.串的基本操作

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#define Max 8
#define OVERFLOW -2
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define INFEASIBLE -1
typedef int Status;


//串的定长顺序存储(数组中下标为0的分量闲置不用,从下标为1的分量开始储存)
typedef struct{
    char ch[Max+1]; //存储串的一维数组
    int length;     //串的当前长度
}SString;           //定长顺序串包括一个确定长度的数组,和一个整型变量


//串的堆式顺序存储
//堆在程序的执行中动态的为每个新产生的串动态分配一块实际串长所需的存储空间
//若分配成功,则返回一个指向起始地址的指针,作为串的基址
typedef struct 
{
    char* ch;   //若是非空串,则按串长分配存储区,否则ch为null
    int length;  //串当前长度
}HString;


//串的链式存储结构
#define CHUNKSIZE 80  //可由用户定义的块的大小(每个节点用于存贮字符串的存储空间大小)
typedef struct Chunk{
    char ch[CHUNKSIZE];
    struct Chunk* next;  //指向块(结点)自身的指针
}Chunk;                  //块(结点)
typedef struct{
    Chunk* head,*tail;    //串的头和尾指针
    int length;           //串的当前长度
}LString;                 //链式串包含串的头,尾指针和结点个数


//BF算法
int Index_BF(SString S,SString T,int pos){
    //返回模式T在主串S中第pos个字符开始第一次出现的位置,若不存在则返回0
    int i=pos;
    int j=1;//i和j分别指示主串S和模式T正在比较的位置
    while(i<=S.length&&j<=T.length){
        if(S.ch[i]!=T.ch[j]) {
            i=i-j+2;//注意i要回溯到i-j+2
            j=1;
        }
        else{
            i++;
            j++;
        }
    }
    if (i>S.length) return 0;
    //i超过主串S长度就说明把主串遍历完都没有匹配成功,j超过模式T长度则说明匹配成功了
    else return i-T.length;
}

1.统计字符串出现的频次

写一个算法统计在输入字符串中各个不同字符(范围是数字和26个大写字母)出现的频度并将结果输出

#include <stdio.h>
int main(){
    char chs[36];
    int nums[36];//存放各个字符出现的频次
    char ch;
    int a;
    for(int i=0;i<36;i++){
        nums[i]=0;
        chs[i]='\0';
    }
    printf("please input string:");
    while((ch=getchar())!='\n'){//字符串以回车键为终止
        if(ch>='0'&&ch<='9') {
            a=ch-'0';
            nums[a]++;
            chs[a]=ch;
        }
        if(ch>='A'&&ch<='Z') {
            a=10+ch-'A';
            nums[a]++;
            chs[a]=ch;
        }
    }
    for(int i=0;i<36;i++){
    	if(nums[i]>0)
        printf("%c:%d\n",chs[i],nums[i]);
    }
}

2.用递归方法实现串的逆序存储(非逆序打印)

== 写一个递归算法来实现字符串逆序存储,要求不另设串存储空间 ==
主要思想还是每次递归交换对称位置上的元素,还是需要好好体会一下递归的思想

#include <stdio.h>
int my_strlen(char * str)  //计算字符串长度
{
	int count = 0;
	while (*str !='\0')
	{
		count++;
		str++;
	}
	return count;
}
void reverse_string(char * str){
 	int len = my_strlen(str);
	char temp = *str;
	*str = str[len - 1];
	str[len - 1] = '\0';//每次把str的第一个元素存储到temp中,并且把最后一个元素放在第一个的位置,再把最后一个赋'\0'
	if (my_strlen(str+1) >= 2)  
	{
		reverse_string(str + 1);//再把指针往后移一位进入下一次递归,此时传入的str长度比之前减2(这次递归就相当于处理原来str第二个和倒数第二个元素)

	}//不断递归直到处理好原来str的所有元素对(传入的str只剩1个或者0个元素时停止递归),此时str前半部分是原来str的后半部分的逆序,而后半部分都是'\0'
	str[len - 1] = temp;//最后一次递归,temp存储的是原来str前半部的最后一个,把它放在现在str的后面
	//返回上一次递归,temp存储的是原来str前半部的倒数第二个元素,注意此时的len比后一次递归式大1的
	//如此就把原str后半部分依次填充完毕(有点想出栈的感觉,先入后出,后入先出)
}
int main()
{
	char arr[10];
	printf("please input the string:");
	scanf("%s",&arr);
	reverse_string(arr);
	printf("%s\n", arr);
	return 0;
}

参考:

#include<stdio.h>
fun(char *a)
{
	if(*a)
	{
		fun(a+1);
		printf("%c",*a);
	}
	
 } 
 int main()
 {
 	char s[10]="abcde";
 	printf("处理前字符串=%s\n处理后字符串=",s);
 		fun(s);
 	printf("\n");
 }
  • 递归后再printf会先进后出,后进先出

3. 字符串的插入

要求设计函数insert将字符串t插入到字符串s中,插入位置为pos(假设分配给字符串s的空间足够让字符串t插入)

//想法:1.定义新的串f保存字符串s位置pos后的字符串(如果t的存储空间够的话直接在t后面操作即可),
//然后对字符串s位置pos后的位置依次以t和f覆盖
//2.或者先计算出串t的长度m,把s位置pos后的字符串都往后移m个,然后把t插入到s的pos位置上
//下面的代码采用想法1
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#define Max 50

void insert(char* s,char* t,int pos){
   char* p=s+pos;//定位到要插入的位置的指针
   int m=0;
   while(t[m]!='\0') m++;//计算t的长度
   while(*p!='\0'){//把s位置pos后的字符串接到t后面
       t[m]=*p;
       p=p+1;
       m++;
   }
   t[m]='\0';//此时t字符串保存着原来的t,以及串s中位置pos后的字符串,最后再加一个'\0'
   p=s+pos;//定位到要插入的位置的指针
   m=0;
   while(t[m]!='\0') {//从串s中位置pos用t覆盖
       *p=t[m];
       p++;
       m++;
   }
    *p='\0';
}


int main(){
    char s[Max];
    char t[Max];
    int pos=3;
    printf("please input string s:");
    scanf("%s",&s);
    printf("please input string t:");
    scanf("%s",&t);
    insert(s,t,pos);
    printf("%s",s);
}

4.已知字符串S1中存放一段英文,写出算法format(s1,s2,s3,n),将其按给定的长度n格式化成两端对齐的字符串s2, 其余的字符送s3

数组

  • 数组是由相同类型的数据元素构成的有序集合
  • 在c语言中,一个二维数组类型可以定义为其分量类型为一维数组类型的一维数组类型,类似于矩阵可以看作是行向量的一维数组,或者列向量的而一维数组
typedef ElemType Array2[m][n];

等价于

typedef ElemType Array1[n];//一阶n维列向量
typedef Array1 Array2[m];//m个n维列向量构成二阶m*n维张量,即矩阵

碎碎念:数据结构里称矩阵是二维数组,但数学中矩阵实际上是二阶张量,维度是用来形容每一阶方向上分量个数的,比如一阶n维就是(x1,x2,…,xn),每个量用一个下标索引即可,索引范围1-n,二阶m*n维张量,就是大小m*n的矩阵,每个元素由两个下标索引,范围分别是1-m,1-n,由此可以推广到n阶张量。

  • 数组的顺序存储:由于数组一般不做插入或删除操作,一旦建立数组后,元素个数和关系不发生变化,所以用顺序存储结构
    存储方法分为以行序为主序和以列序为主序两种,以行序为主序,就是第一行存完了,把第二行接在第一行后面继续存…
  • 矩阵(下标从1开始)第i,j个元素存在a[i-1][j-1](下标从0开始)中,在物理存储结构中位置是&a[0][0]+[n*(i-1)+j-1]*L,L是每个数据元素占据的存储单元大小

5.判断二维数组是否有重复元素

设二维数组a[1…m,1…n]含有m*n个整数:写一个算法判断a中所有元素是否互不相同?输出相关信息并分析时间复杂度
想法:暴力两层循环:a[1,1]与之后的a[1,2],a[1,3]…依次比较,然后再a[1,2]与它后面的元素依次比较…比较次数为mn-1 + mn-2 +…+ mn-(mn-1)=O(m2n2)(即mn个数两两之间有一次对比,即mn(mn-1)/2)

#include <stdio.h>
int main(){
    int m,n,flag=0;
    printf("m=:");scanf("%d",&m);
    printf("n=:");scanf("%d",&n);
    int a[m][n];
    printf("please input matrix a:");
    for(int i=0;i<m;i++){//输入二维数组
        for(int j=0;j<n;j++){
            scanf("%d",&a[i][j]);
        }
    }
    for(int i=0;i<m*n-2;i++){//比较元素是否有相同
        for(int j=i+1;j<m*n-2;j++){
            if(*(&(a[0][0])+i)==*(&(a[0][0])+j)){//这边不能写if(*(a+i)==*(&a+j)){ 会报错,虽然好像数组名就是首元素的地址啊
                flag=1;
                break;
            }
        }if(flag) break;
    }
    if(flag) printf("repeat!");
    else printf("no repeat!");
    //打印数组
    for(int i=0;i<m;i++){
        for(int j=0;j<n;j++){
            printf("%d ",a[i][j]);
        }
    }
}

计算复杂度主要是由比较那边的两层循环带来的,最差就是最后两个元素是一样的,那么执行次数为O(m 2 ^2 2 *n 2 ^2 2)

  • 高维数组的顺序存储都能看成是一维数组,如此处理便可以
  • 这边要注意如何跳出两层循环,单独break只能跳出一层循环
  • 判断数组是否有重复元素还可以先排序或者哈希表,等之后学到了再回顾吧

6.分离数组中的正数与负数

设任意m个整数存放于数组A[1…m]中,试编写算法,将所有正数排在负数前面,要求时间复杂度为O(m)
想法:设置指针i,j分别用来寻找上一次循环的i扫描到的位置后的第一个负数和在i后面的正数,然后交换它们的值,这样能保证每次循环i之前的数都是排好序的
也就是说每次循环i都往后扫描直到遇到负数停下来,这时候从这个位置开始,j往后扫描,扫描到正数停下来,交换它们的值,把负数换到后面,正数换到前面,这样i以及i前面所有的都是正数,如果j=m说明某正数后面都是负数了,就结束

#include <stdio.h>
int main(){
    int m;
    printf("m=");
    scanf("%d",&m);
    int A[m];
    for(int i=0;i<m;i++){
    scanf("%d",&A[i]);}
    int i=0,j=0;  
    while(A[i]>0&&i<m) i++;//定位到第一个负数上
    j=i+1;
    while(A[j]<=0&&j<m) j++;//定位到i后面的第一个正数
    if(i==m||j==m) {
        for(int i=0;i<m;i++) 
        printf("%d ",A[i]);
        return 0;}
    while(j<m){
        int temp=A[i];
        A[i]=A[j];
        A[j]=temp;
        while(A[i]>0) i++;//定位到负数上
        j=i+1;
        while(A[j]<=0) j++;//定位到i后面的第一个正数
    }
    for(int i=0;i<m;i++)
    printf("%d ",A[i]);
    return 0;
}

注:这个想法其实不是很自然,网上更自然的想法是i从头开始扫描,到负数停止,j从尾向前扫描,到正数停止,然后交换它们的值,然后i继续向后,j继续向前,直到i>=j停止

#include <stdio.h>
int main(){
    int m;
    printf("m=");
    scanf("%d",&m);
    int A[m];
    for(int i=0;i<m;i++){
    scanf("%d",&A[i]);}
    int i=0,j=m-1;
    while(A[i]>0&&i<m) i++;//定位到第一个负数上
    while(A[j]<=0&&j>0) j--;//定位最靠后的正数
    if(i==m||j==0) {
        for(int i=0;i<m;i++) 
        printf("%d ",A[i]);
        return 0;}
    while(i<j){
        int temp=A[i];
        A[i]=A[j];
        A[j]=temp;
        while(A[i]>0&&i<m) i++;//继续往后搜寻负数
        if(i>=j) break;
        while(A[j]<=0&&j>0) j--;//继续往前搜寻正数
    }
    for(int i=0;i<m;i++)
    printf("%d ",A[i]);
    return 0;
}

啊暑假都要过去了,自学了快要一个多月了啥时候能学完呢

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值