05、C语言3.0
1、排序
1、冒泡排序
遍历原始数据,从第一个数开始,到倒数第二个数结束,比较这个数和下一个数的大小,如果这个数比下一个数大,则交换这两个数。这样便可以将数据中最大的数转移到数组的最后。
之后再次遍历原始数据,但是变为从第一个数开始,到倒数第三个数结束,比较这个数和下一个数的大小,如果这个数比下一个数大,则交换这两个数。这样便可以将第二大的数转移到数组的倒数第二位。
重复执行上述过程,一直到从第一个数开始,到第二个数结束,从而完成了排序过程。
#include<stdio.h>
#define ArrayLen(a) sizeof(a)/sizeof(a[0])
int bublle(int *a,int len)
{
int temp;
for(int i=0;i<len-1;i++)
{
int flag=0;//设置标志位
for(int j=0;j<len-i-1;j++)
{
if(a[j]>[j+1])//当前面一个数大于后面一个数,交换他们的位子
{
temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
flag=1;
}
}
if(flag == 0)
{
break;
}
}
}
int main()
{
int a[]={3,1,4,7,5};
bulle(a,ArrayLen(a));
for(int i=0;i<ArrayLen(a);i++)
{
printf("%d\t",a[i]);
}
return 0;
}//时间复杂度为:o(n^2)
优化冒泡排序:借用回调函数指定相关规则使得冒泡排序可以由自己随意选择顺序还是逆序
#include<stdio.h>
#define Arraylen(a) sizeof(a)/sizeof(a[0])//返回数组长度
int IsSmaller(int a,int b)
{
if(a<b)
return 1;
else
return 0;
}
int IsBigger(int a,int b)
{
if(a>b)
return 0;
else
return 1;
}
int BublleSort(int *a,int len,int (*rule)(int,int))
{
int temp;
for(int i=0;i<len-1;i++)
{
int flag = 0;//设置标记位检查看是否有数据未发生交换,为0则没有交换,为1则发生了交换
for(int j=0;j<len-i-1;j++)
{
if(rule(a[j],a[j+1]))//使用回调函数,由使用者自行决定要选择逆序还是顺序输出
{
temp = a[j+1];
a[j+1] = a[j];
a[j] = temp;
flag = 1;
}
}
if(flag == 0)
{
break;
}
}
}
int main()
{
int a[]={2,4,6,3,1,9,5,7,8};
BublleSort(a,Arraylen(a),IsSmaller);
for(int i=0;i<Arraylen(a);i++)
{
printf("%d\t",a[i]);
}
printf("\n");
return 0;
}
2、插入排序
适用于:基本有序的数列
插入排序的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
#include<stdio.h>
#define ArrayLen(a) sizeof(a)/sizeof(a[0])
void InsertSort(int *a,int len)
{
for(int i=1;i<len;i++)
{
int temp=a[i];
int j=0;
for(j=i-1;j>=0;j--)
{
if(a[j]<a[j+1])
{
a[j+1]=a[j];
}
else
{
break;
}
}
a[j+1]=temp;
}
}
int main()
{
int a[]={4,2,1,6,3};
InsertSort(a,ArrayLen(a));
for(int i=0;i<ArrayLen(a);i++)
{
printf("%d\t",a[i]);
}
return 0;
}
3、选择排序
工作原理:第一次从待排序的数据元素中选择出最小或者最大的一个元素,存放在序列的起始位置;然后再从剩余的未排序元素中寻找到最小或者最大的元素,然后放到未排序序列的起始位置;重复该步骤直至序列完全结束。
#include<stdio.h>
#define Arraylen(a) sizeof(a)/sizeof(a[0])
void ChooseSort(int *a,int len)
{
if(len=0||len=1)
return ;
for(int i=0;i<len;i++)
{
int min=i;
int minum = a[min];
for(int j=i+1;j<len;j++)
{
if(a[j]<a[min])
{
minum = a[j];
min = j;
}
}
int temp = a[i];
a[i] = a[min];
a[min] = temp;
}
}
int main()
{
int a[]={1,4,3,8,2,5,6};
ChooseSort(a,Arraylen(a));
return 0;
}
4、快速排序
一种原地排序,只需要一个很小的栈作为辅助空间,空间复杂度为O(logN),所以适合在数据集比较大且无序的时候使用。
算法思想:
① 从数列中挑出一个元素,称为 “基准”(pivot),
② 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
③ 递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。
算法实现:
#include<stdio.h>
#define Arraylen(a) sizeof(a)/sizeof(a[0])
void QuickSort(int *a,int start,int end)
{
int temp=a[start];
int left=start;
int right=end;
while(left<right)
{
while(left<right&&a[right]>temp)//找到右边比基准值小的那个数停
{
right--;
}
if(left<right)
{
a[left]=a[right];
left++;
}
while(left<right&&a[left]<temp)//找到左边比基准值大的那个数停
{
left++;
}
if(left<right)
{
a[right]=a[left];
right--;
}
a[left]=temp;
QuickSort(a,start,left-1);
QuickSort(a,right+1,end);
}
}
int main()
{
int a[] = {5, 3, 9, 2, 0 ,4, 6};
QuickSort(a,Arraylen(a));
return 0;
}
方法二:
void fastSort(int *a,int start,int end)
{
if(start>end)
return ;
int temp=a[start];
int i=start;
int j=end;
while(i!=j)
{
while(i<j&&a[j]>=temp)
j--;
while(i<j&&a[i]<=temp)
i++;
if(j>i)
{
int t=a[i];
a[i]=a[j];
a[j]=t;
}
}
a[start]=a[i];
a[i]=temp;
FastSort(a,start,i-1);
FastSort(a,i+1,end);
}
2、递归
递归就是指在方法的定义中使用方法自身。也就是说,递归算法是一种直接或者间接调用自身方法的算法。简言之:在定义自身的同时又出现自身的直接或间接调用。
//猴子吃桃子问题:猴子每天吃当天桃子数量的一半+1;第十天的时候只剩下一个桃子,第一天有几个桃子
#include<stdio.h>
int EatPeach(int day)
{
if(day==10)
return 1;
else
return ((EatPeach(day+1)+1)*2);
}
//求n! n! = n * (n-1)!
int factorial(int n)
{
if(n==1 || n==0)
return 1;
else
return n*factorial(n-1);
}
//计算字符串长度
int MystrLen(char *s)
{
if(*s == '\0')
return 0;
else
return (MystrLen(s+1)+1);
}
int main()
{
printf("%d\n",MystrLen("helloworld"));
printf("%d\n",factorial(10));
printf("%d\n",EatPeach(8));
return 0;
}
3、二维数组
1、二维数组定义:
多个一维数组的集合;int a[ ][ ]={{1,2,3},{3,4,5}};
2、二维数组的初始化:
必须初始化列数(步长),否则出错
eg:int a[][]={1,2,3,4,5,6};int a[2][]={1,2,3,4,5,6}
都出错
3、相关概念
a:首行的地址 a+1:代表跨一行地址 和a[0]相同
&a:整个数组的首地址 &a+1:跨一整数组的长度
&a[0]:取首行地址 &a[0]+1:跨一行的地址
&a[0] [0] :首个元素的地址 &a[0] [0]+1:跨一个元素的地址
#include<stdio.h>
int main()
{
int a[2][3] ={{1,2,3},{4,5,6}};
int b[2][3] ={1,2,3,4,5,6};
for(int i=0;i<2;i++)
{
for(int j=0;j<3;j++)
{
printf("%x ",&a[i][j]);
}
printf("\n");
}
printf("%X %x %x %x\n",a,&a,&a[0],&a[0][0]);//输出相同地址
printf("%X %x %x %x\n",a+1,&a+1,&a[0]+1,&a[0][0]+1);
printf("%d",*((*a)+1));
return 0;
}//*(*(a+i)+j)相当于a[i][j];
int (*p)[3]
数组指针:一个指向一个有三个int
的数组的指针。和数组本身的指针表示一样;p+1表示跨一行既是12个字节。是一个二级指针,指向一个数组,* *代表取值。
int * p[3]
指针数组:这个数组里有三个指针,p+1跨一个元素,但是整个指针数组里的元素都是指针,所以跨一个元素就是一个指针的大小8个字节;
int a[5][5]={0};
int (*p)[5] = a;
printf("%x\n",p);
printf("%x\n",p+1);//输出为1707b650 1707b664跨越20个字节长度
int *p[5]=a;
printf("%x\n",p);//指针数组首元素的地址 *p代表p[0],p[0]是指向a首行的地址,**p代表取值
printf("%x\n",p+1);//输出为63b35340 63b35348跨越8个字节长度
二维数组的应用:
- 1、杨辉三角:(a+b)^n 展开式项前面的系数
1 1
1 1 1 1
1 2 1 ----------> 1 2 1
1 3 3 1 1 3 3 1
1 4 6 4 1 1 4 4 6 1
#include<sdtio.h>
#define Size 10
int main()
{
int a[Size][Size]={0};
for(int i=0;i<Size;i++)
{
a[i][0]=1;
a[i][i]=1;
}
for(int i=2;i<Size;i++)
{
for(int j=1;j<i;j++)
{
a[i][j]=a[i-1][j-1]+a[i-1][j];
}
}
for(int i=0;i<Size;i++)//输出杨辉三角
{
for(int j=0;j<Size-i;j++)
{
printf(" ");
}
for(int j=0;j<=i;j++)
{
printf("%5d",a[i][j]);
}
printf("\n");
}
printf("\n");
return 0;
}
- 2、设置“井”字棋游戏
#include<stdio.h>
#define Size 3
#define true 1
#define false 0
//初始化二维数组的每一个元素
void InitBoard(char (*Board)[Size])
{
for(int i=0;i<Size;i++)
{
for(int j=0;j<Size;j++)
{
Board[i][j]=' ';
}
}
}
//输出棋盘
void PrintBoard(char (*Board)[Size])
{
printf("\n");
for(int i=0;i<Size;i++)
{
for(int j=0;j<Size;j++)
{
printf(" %c ",a[i][j]);
if(j!=Size-1)
printf("|");
}
printf("\n");
if(i!=Size-1)
printf("---|---|---");
}
printf("\n");
}
//判断下棋的位置是否合法,合法则输出
int Makemove(char (*Board)[Size],int row,int column,char player)
{
if(row <0 ||row >=Size ||column <0||column >=Size)
{
printf("Illegal place!please try again!\n");
return false;
}
if(Board[row][column]!=' ')
{
printf("The place has been ocupied!please try again!\n");
return false;
}
Board[row][column]=player;
return true;
}
//判断赢棋的条件
int CheckisWin(char (*Board)[Size],char player)
{
for(int i=0;i<size;i++)
{
if(Board[i][0]==player&&Board[i][1]==player&&Board[i][2]==player)
return true;
}
for(int i=0;i<size;i++)
{
if(Board[0][i]==player&&Board[1][i]==player&&Board[2][i]==player)
return true;
}
if(Board[0][0]==player&&Board[1][1]==player&&Board[2][2]==player)
return true;
if(Board[0][2]==player&&Board[1][1]==player&&Board[2][0]==player)
return true;
}
//判断是否平局
int CheckisDraw(char (*Board)[Size])
{
for(int i=0;i<Size;i++)
{
for(int j=0;j<Size;j++)
{
if(Board[i][j]==' ')
return false;
}
}
return true;
}
int main()
{
char Board[Size][Size]={0};
InitBoard(Board);
char player='X';
while(1)
{
PrintBoard(Board);
printf("Player %c please enter your mark:",player);
int row=0,column=0;
scanf("%d %d",&row,&column);
if(Makemove(Board,row-1,column-1,player)==false)
{
continue;
}
if(CheckisWin(Board,player)==true)
{
PrintBorad(Board);
printf("Player %c wins!Congratulations!\n",player);
break;
}
if(CheckisDraw(Borad)==true)
{
PrintBorad(Board);
printf("The result is Draw!Game Over!\n");
break;
}
player = (player == 'X')?'X':'O';
}
return 0;
}
- 3、完成五子棋游戏
#include<stdio.h>
#define Size 15
#define true 1
#define false 0
//初始化棋盘内容
void InitBoard(char (*Board)[Size])
{
for(int i=0;i<Size;i++)
{
for(int j=0;j<Size;j++)
{
Board[i][j]='-';
}
}
}
void PrintBoard(char (*Board)[Size])
{
printf(" ");
for(int i=0;i<Size;i++)
{
printf(" %2d ",i+1);
}
printf("\n");
for(int i=0;i<Size;i++)
{
printf("%2d ",i+1);
for(int j=0;j<Size;j++)
{
printf(" %c ",Board[i][j]);
}
printf("\n");
}
printf("\n");
}
int MakeMove(char (*Board)[Size],int row,int column,char player)
{
if(row <0 || row >= Size || column <0 ||column >= Size)
{
printf("Illegal place!please try again!\n");
return false;
}
if(Board[row][column] != '-')
{
printf("This place has been ocupied!try again!\n");
return false;
}
Board[row][column] = player;
return true;
}
int CheckisWin(char (*Board)[Size],int row,int column,char player)
{
int direction[4][2] =
{
{1,0},
{0,1},
{1,1},
{-1,1}
};
for(int i=0;i<4;i++)
{
int count = 1;
int dx = row + direction[i][0];
int dy = column + direction[i][1];
while(dx >=0 && dx < Size && dy >= 0 && dy < Size && Board[dx][dy]==player)
{
count++;
dx = dx + direction[i][0];
dy = dy + direction[i][1];
}
dx = row - direction[i][0];
dy = column - direction[i][1];
while(dx >=0 && dx < Size && dy >= 0 && dy < Size && Board[dx][dy]==player)
{
count++;
dx = dx - direction[i][0];
dy = dy - direction[i][1];
}
if(count >= 5)
{
return true;
}
}
return false;
}
int CheckIsDraw(char (*Board)[Size])
{
for(int i=0;i<Size;i++)
{
for(int j=0;j<Size;j++)
{
if(Board[i][j] == '-')
return false;
}
}
return true;
}
int main()
{
char Board[Size][Size]={0};
InitBoard(Board);
char player = 'X';
while(1)
{
PrintBoard(Board);
printf("Player %c please enter your mark:",player);
int row=0,column=0;
scanf("%d %d",&row,&column);
if(MakeMove(Board,row-1,column-1,player)==false)
{
continue;//跳过当次循环
}
if(CheckisWin(Board,row-1,column-1,player)==true)
{
PrintBoard(Board);
printf("Player %c wins!\n",player);
break;
}
if(CheckIsDraw(Board)==true)
{
PrintBoard(Board);
printf("The result is Draw!Game Over!\n");
break;
}
player = (player == 'X')? 'O' : 'X';
}
return 0;
}
4、二级指针
指向指针的指针,跨一个代表跨一个数组的长度,需要两个*进行取值。
一级指针跨一个代表跨一个元素的大小;一个*取值
#include<stdio.h>
int main()
{
int a=10;
int *ptr = &a;
int **ptr2 = &ptr;
printf("%d ",**ptr2);;
return 0;
}
当设置一个一级指针p指向一个二维数组时,因为二维数组在内存中以一维数组的形式存储,所以当设置一个一级指针对其取值时,p++就是跨一个指针类型的长度。
#include<stdio.h>
int main()
{
int a[2][3] = {1,2,3,4,5,6};
int *p=&a[0];//p是一个一级指针,指向数组元素首地址
p++;//跨4个字节,数组一个元素的大小
printf("%d\n",*p);//2
int (*q)[3] = a;//数组指针,二级指针,指向a数组的首地址
q++;//跨12字节一整数组的长度
printf("%x\n",*q);//输出为a[4]的地址,想要取值就是*(*(p+i)+j)取a[i][j]
int (*w)[3]=&a[0][0];//数值指针,二级指针,指向a数组首元素的地址
printf("%d\n",**w);//对他取值:结果为1
int *p1[3];//指针数组,里面每一个元素都是一个一级指针,p1代表指针数组的首地址
*(p1+1) = a[1];//p1+1代表指向a[1]这一行,把a[1]的地址给p1[1]
printf("%d\n",*p1[1]);//对a[1]这一行的首元素取值为4
return 0;
}
相关测试题:
a) 一个整型数 int a
;
b)一个指向整数型的指针 int *a;
c)一个指向指针的指针,它指向的指针是指向一个整型数 int **a;
d)一个有10个整型数的数组 int a[10];
e)一个有10个指针的数组,该指针是指向一个整型数 int *a[10];
f)一个指向有10个整型数数组的指针 int (*a)[10];
g)一个指向函数的指针,该函数有一个整型参数并返回一个整型数 int (*p)(int)
h)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数 int (*a[10])(int)
函数指针数组
i) int ((*(*complexFunctionPointerArray)[5]))(char*,double);
指向函数指针数组的指针
j) char * (*(*complexFunctionPointerArray)(int,int))[10];
指向函数指针的指针数组
5、main函数
main函数,又称主函数,作为绝大部分C程序唯一的入口,是要求有返回值的,该返回值返回给(如操作系统)来表明该程序的执行状况。返回0代表程序正常执行成功,返回非0值代表程序异常结束,因此返回值需要是int
整型,于是有了int main()
的规范。
int main(int argc,char *argv[])
- 参数
void
:不接受任何参数;argc
:代表程序所运行环境传递给程序的参数数量;argv
:指向argc+1
个指针的数组的首元素的指针。数组末元素为空指针,而若前面有元素,则它们指向表示从宿主环境传递给程序的参数的字符串。若argv[0]
不是空指针(或argc>0
),则它指向表示程序名的字符串。若程序名从宿主环境不可用则该字符串为空。
#include<stdio.h>
#include<string.h>
#define true 1
#define false 0
typedef char* CharPiont;
int login(CharPoint (*str)[2],const char *account,const char *password)
{
if(strcmp(account,"admin")==0)//字符串比较,匹配超级用户账号
{
if(strcmp(password,"123456") == 0)
{
printf("welcome!admin!\n");
return true;
}
}
for(int i=0;i<3;i++)//从第一行的用户开始
{
if(strcmp(str[i][0],account)==0)//匹配相应的账号
{
if(strcmp(str[i][1],password)==0)//匹配相应的密码
{
printf("welcome!\n");
return true;
}
}
}
return false;
}
void SignUp(CharPoint (*str)[2],counst char *account,const char *password)
{
for(int i=0;i<4;i++)
{
if(strcmp(str[i][0],"")==0)
{
str[i][0] = account;
str[i][1] = password;
}
}
}
int main(int argc,char *argv[])
{
char *account[3][2]=
{
{"zhangsan","1"},
{"lisi","2"},
{"wangwu","3"}
};//存储其他用户账号,密码
if(argc !=3)//传入命令参数的数量,其中./admin也代表了一个数量
{
printf("invalid input number!\n");
return 0;
}
SignUp(account,argv[1],argv[2]);
if(login(account,argv[1],argv[2])==false)//调用login函数判断输入的账号密码是否正确
{
printf("account or password error!please input again!\n");
}
for(int i=0;i<4;i++)
{
for(int j=0;j<2;j++)
{
printf("%s ",account[i][j]);//输出账号和密码
}
printf("\n");
}
printf("\n");
return 0;
}
//使用./1 admin 123456输出welcome!admin!
//使用./1 zhangsan 1输出welcome!