0
点赞
收藏
分享

微信扫一扫

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?


1.C提供形如

#include <filename> 的语句,它读入文件filename并将其插到include语句处,include语句可以嵌套,换句话说,文件filename本身还可以包含include语句,但是显然一个文件在任何链接中不能包含它自己,编写一个程序,是它读入被include语句修饰的一个文件,并输出这个文件。

为什么递归调用要求不能包含它自己?因为那样会破坏递归要求有终点的约定,递归是重复调用,但不是无限调用,递归一定是有结束的,从形式上说,递归类似于无环的树结构,而如果文件链接包含的不是其它文件,而是自己,则会引入依赖环。

#include<iostream>
#include<string>
#include<vector>
#include<fstream>
#include<algorithm>
using namespace std;

vector<string> files;

void ProcessFile(const string& filename)
{
string line;
ifstream in(filename);
if (in.fail()) {
cerr << "fail to open file " << filename << endl;
return;
}
files.push_back(filename);
while (getline(in, line)) {
if (line.find("#include") != string::npos) {
string::size_type beg = line.find_first_of("<\"");
string::size_type end = line.find_last_of(">\"");
string file = line.substr(beg + 1, end - beg - 1);
if (find(files.begin(), files.end(), file) != files.end()) {
cerr << "Existed file " << file << endl;
continue;
} else {
ProcessFile(file);
}
} else {
cout << line << endl;
}
}
in.close();
}

int main()
{
ProcessFile("test.h");
return 0;
}

编译:

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_c++

头文件内容

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_c++_02

运行结果:

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_c++_03

2.算法时间复杂度分析中几种典型的增长率函数。

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_#include_04

3.快速排序的经典实现:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


//快速排序算法(从小到大)
//arr:需要排序的数组,begin:需要排序的区间左边界,end:需要排序的区间的右边界
void quickSort(int *arr,int begin,int end)
{
//如果区间不只一个数
if(begin < end)
{
int temp = arr[begin]; //将区间的第一个数作为基准数
int i = begin; //从左到右进行查找时的“指针”,指示当前左位置
int j = end; //从右到左进行查找时的“指针”,指示当前右位置
//不重复遍历
while(i < j)
{
//当右边的数大于基准数时,略过,继续向左查找
//不满足条件时跳出循环,此时的j对应的元素是小于基准元素的
while(i<j && arr[j] > temp)
j--;
//将右边小于等于基准元素的数填入右边相应位置
arr[i] = arr[j];
//当左边的数小于等于基准数时,略过,继续向右查找
//(重复的基准元素集合到左区间)
//不满足条件时跳出循环,此时的i对应的元素是大于等于基准元素的
while(i<j && arr[i] <= temp)
i++;
//将左边大于基准元素的数填入左边相应位置
arr[j] = arr[i];
}
//将基准元素填入相应位置
arr[i] = temp;
//此时的i即为基准元素的位置
//对基准元素的左边子区间进行相似的快速排序
quickSort(arr,begin,i-1);
//对基准元素的右边子区间进行相似的快速排序
quickSort(arr,i+1,end);
}
//如果区间只有一个数,则返回
else
return;
}

int main(void)
{
int num[12] = {23,45,17,11,13,89,72,26,3,17,11,13};
int n = 12;

quickSort(num,0,n-1);
printf("排序后的数组为:\n");
for(int i=0;i<n;i++)
printf("%d ", num[i]);

printf("\n");
return 0;
}

测试结果:

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_#include_05

快速排序为什么要从基准数的对面开始移动?

以自然序排序为例
如果选取最左边的数arr[left]作为基准数,那么先从右边开始可保证i,j在相遇时,相遇数是小于基准数的,交换之后temp所在位置的左边都小于temp。但先从左边开始,相遇数是大于基准数的,无法满足temp左边的数都小于它。所以进行扫描,要从基准数的对面开始。

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_数据结构_06

可以思考一下这幅图和上面逻辑的相似之处

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_#include_07

一个堆排序的C语言实现:

#include <stdio.h>
#include <stdlib.h>

static unsigned int input[] = {1, 3, 4, 5, 2, 6, 9, 7, 8, 0};
void heap_adjust(unsigned int *data, int parent, int length)
{
unsigned int temp = data[parent];
unsigned int child = 2 * parent + 1;

while(child < length)
{
if((child + 1 < length) && input[child] < input[child + 1])
{
child ++;
}

if(temp >= input[child])
{
break;
}

input[parent] = input[child];

parent = child;
child = 2 * child + 1;
}

input[parent] = temp;
}

static void dump(int time, unsigned int *buf)
{
int i;

printf("\ntimes %d: ", time);
for (i = 0; i < 10; i ++)
{
printf("%d ", buf[i]);
}
//printf("\n");
}

int main(void)
{
int i;

for(i = (sizeof(input)/sizeof(input[0])) / 2; i >= 0; i --)
{
heap_adjust(input, i, sizeof(input)/sizeof(input[0]));
}

for(i = (sizeof(input)/sizeof(input[0])) -1; i > 0; i --)
{
int temp = input[i];
input[i] = input[0];
input[0] = temp;

heap_adjust(input, 0, i);
dump(10 - i, input);
}
printf("\n");
return 0;
}

czl@czl-VirtualBox:~/paixu$ ./a.out 

times 1: 8 7 6 5 2 1 4 3 0 9
times 2: 7 5 6 3 2 1 4 0 8 9
times 3: 6 5 4 3 2 1 0 7 8 9
times 4: 5 3 4 0 2 1 6 7 8 9
times 5: 4 3 1 0 2 5 6 7 8 9
times 6: 3 2 1 0 4 5 6 7 8 9
times 7: 2 0 1 3 4 5 6 7 8 9
times 8: 1 0 2 3 4 5 6 7 8 9
times 9: 0 1 2 3 4 5 6 7 8 9
czl@czl-VirtualBox:~/paixu$

深度有限搜索递归算法

下面的代码会通过block数组将死胡同的路径标出来。

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

int n, m, p, q, min=99999999;
int a[5][4], book[5][4], block[5][4];
#define FAKE_BLOCK 2

bool dfs(int prex, int prey, int x, int y, int step)
{
int next[4][2] = \
{{0, 1}, //left
{1, 0}, //down
{0, -1}, //right
{-1, 0}};//up

int tx, ty, k;

if(x == p && y == q)
{
printf("%s line %d, step %d.\n", __func__, __LINE__, step);
if(step < min)
min = step;

return true;
}

bool status = false;
for(k = 0; k <= 3; k ++ )
{
tx = x + next[k][0];
ty = y + next[k][1];

if(tx < 0 || tx >= n || ty < 0 || ty >= m)
continue;

if(a[tx][ty] == 0 && book[tx][ty] == FAKE_BLOCK &&!(tx==prex && ty==prey))
{
status = true;
continue;
}
else if(a[tx][ty] == 0 && book[tx][ty] == FAKE_BLOCK)
{
continue;
}

if(a[tx][ty] == 0 && book[tx][ty] == 0)
{
book[tx][ty] = FAKE_BLOCK;

if (true == dfs(x, y, tx, ty, step + 1))
{
status = true;
}
else
{

}

book[tx][ty] = 0;
}
}

if(status == true)
{
printf("x=%d,y=%d.\n", x, y);
}
else
{
printf("[%d,%d]->[%d,%d] block.\n", prex, prey, x, y);
block[x][y] = 1;
}

return status;
}

int main(void)
{
int i, j, startx, starty;

n = 5;
m = 4;

startx = 0;
starty = 0;

p = 3;
q = 2;

a[0][0] = 0; a[0][1] = 0; a[0][2] = 1; a[0][3] = 0;
a[1][0] = 0; a[1][1] = 0; a[1][2] = 0; a[1][3] = 1;
a[2][0] = 0; a[2][1] = 0; a[2][2] = 1; a[2][3] = 0;
a[3][0] = 0; a[3][1] = 1; a[3][2] = 0; a[3][3] = 0;
a[4][0] = 0; a[4][1] = 0; a[4][2] = 0; a[4][3] = 1;

book[startx][starty] = FAKE_BLOCK;

dfs(-1, -1, startx, starty, 0);

printf("%d\n", min);

printf("=========================================\n");
for (i = 0; i < n; i ++)
{
for(j = 0; j < m; j ++)
{
printf("%d ", a[i][j]);
}
printf("\n");
}
printf("=========================================\n");

printf("=========================================\n");
for (i = 0; i < n; i ++)
{
for(j = 0; j < m; j ++)
{
printf("%d ", book[i][j]);
}
printf("\n");
}
printf("=========================================\n");

printf("=========================================\n");
for (i = 0; i < n; i ++)
{
for(j = 0; j < m; j ++)
{
printf("%d ", block[i][j]);
}
printf("\n");
}
printf("=========================================\n");

return 0;
}

运行结果:

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_数组_08

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_#include_09

以上代码可能还有些问题:

走法讲解:

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_c++_10

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_数据结构_11

使用栈实现的迷宫算法:

//迷宫问题
#include<stdio.h>
#include<stdlib.h>
#define m 9
#define n 9
#define MAXSIZE 100
//迷宫问题

//定义移动位置 ,其中
typedef struct{
int x,y;
}item;
//定义一个数据类型为dataType,在栈里面存在此数据
//主要作为迷宫移动位置的临时存放点,其中x为当前位置的横坐标,y为当前位置的纵坐标
//d为 搜索的次数(方向)
typedef struct{
int x,y,d;
}dataType;

//定义一个栈
typedef struct{
dataType data[MAXSIZE];
int top;
}list,*pList;


//创建栈

pList initList(){
pList p;
p = (pList)malloc(sizeof(list));
if(p){
p->top = -1;
}

return p;
}

//判断栈是否为空
int isEmpty(pList p){
if(p->top==-1){
return 1;//如果是空栈就返回1
}else{
return 0;
}
}

//压栈
int pushList(pList p,dataType data){
if(p->top==MAXSIZE-1){//如果栈超出了大小,返回0
return 0;
}
p->top++;
p->data[p->top] = data;//压栈操作
return 1;//压栈成功,返回1
}

//弹栈
int popList(pList p,dataType *data){
if(isEmpty(p)){
return 0;//如果是空栈,就不弹,直接返回0
}else{
*data = p->data[p->top];
p->top--;
return 1;
}
}

//销毁栈
void destory(pList *p){
if(*p){
free(*p);
}
*p = NULL;
return;
}

int mazePath(int maze[][n+2],item move[],int x0,int y0){//maze表示一个迷宫的数组,move表示当前探索,x0,y0表示初始位置
pList p;//定义栈
dataType temp;//定义临时位置
int x,y,d,i,j;//x,y用来存放临时位置的角标,d表示临时的探索次数,i,j所在位置的迷宫角标
p = initList();//创建栈
if(!p){//如果创建失败
printf("创建栈失败");
return 0;
}

//初始化走过的位置
temp.x = x0;
temp.y = y0;
temp.d = -1;

//把迷宫入口压栈
pushList(p,temp);

//当栈里面不为空时
while(!isEmpty(p)){
popList(p,&temp);//弹栈
x = temp.x;
y = temp.y;
d = temp.d+1;//给d+1,让其进行第一次探索
while(d<4){//四次探索
i = x+move[d].x;//原来的位置加上探索的位置
j = y+move[d].y;

//当某条路是通路的时候
if(maze[i][j]==0){//表示此路可走
//使用temp来保存路径
temp.x = x;
temp.y = y;
temp.d = d;
//将路径压栈
pushList(p,temp);
//x、y来保存当前新的路径
x = i;
y = j;
maze[x][y] = -1;//把走过的路径的值设为-1

if(x==m && y==n){//判断是否走到头,如果走到头
while(!isEmpty(p)){//如果栈不为空
popList(p,&temp);//弹栈
printf("(%d,%d,%d),<-",temp.x,temp.y,temp.d);//打印路径
}
printf("origin\n");
//程序结束,销毁栈
destory(&p);
return 1;
}else{//如果能走通,但是却没有走完迷宫,把d置为0
d = 0;
}
} else{//如果路不通,换个方向在进行探索
d++;
}
}

}
//如果最后都没找到,说明迷宫没有通路
destory(&p);
return 0;

}

void main(){
item move[4];//定义一个控制探索路径的移动数组
//定义迷宫数组
int maze[11][11]={
{1,1,1,1,1,1,1,1,1,1,1},
{1,0,0,0,1,0,1,1,1,0,1},
{1,0,1,0,0,0,0,1,0,1,1},
{1,0,0,1,0,0,0,1,0,0,1},
{1,1,0,1,0,1,0,1,0,1,1},
{1,0,1,0,1,0,0,1,0,0,1},
{1,0,0,0,0,0,1,0,1,0,1},
{1,1,1,1,0,1,0,0,0,0,1},
{1,0,0,1,0,0,0,1,0,1,1},
{1,0,0,0,0,1,0,1,0,0,1},
{1,1,1,1,1,1,1,1,1,1,1}
};

//定义第一次移动 ,方向为北
move[0].x = 0;
move[0].y = 1;

//定义第二次移动,方向为南
move[1].x = 0;
move[1].y = -1;

//规定第三次移动,方向为东
move[2].x = 1;
move[2].y = 0;

//规定第三次移动,方向为西
move[3].x = -1;
move[3].y = 0;

mazePath(maze,move,1,1);
}

运行:

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_#include_12

冒泡排序:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
int a[10];

int i = 0;
for(i = 0; i < 10; i ++)
{
a[i] = 10-i;
}

for(i = 0; i < 10; i ++)
{
printf("a[%d] = %d.\n", i, a[i]);
}

printf("=======================================================\n");
int j;

for (j = 9; j > 0; j --)
{
for(i = 0; i < j; i ++)
{
if(a[i] > a[i+1])
{
int tmp;
tmp = a[i + 1];
a[i+1] = a[i];
a[i] = tmp;
}
}
}

for(i = 0; i < 10; i ++)
{
printf("a[%d] = %d.\n", i, a[i]);
}

return 0;
}

时间复杂度和空间复杂度分析:

对于N个数据,外循环N-1次,内循环次数依赖于外循环的记数,一共为N-1+N-2+...+1 = N(N-1)/2

次,如果最差情况下,每次都会执行交换,包含三次赋值,则复杂度为 3N(N-1)/2,所以,冒泡排序:

最优的时间复杂度为:O( n^2 ) ;
最差的时间复杂度为:O( n^2 );
平均的时间复杂度为:O( n^2 );

有的说 O(n),下面会分析这种情况,修改程序,当检测到当前循环不需要交换的时候,说明都是正序,这个时候直接跳出循环:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
int a[10];
int flag = 1;

int i = 0;
for(i = 0; i < 10; i ++)
{
/*a[i] = 10-i;*/
a[i] = i;
}

for(i = 0; i < 10; i ++)
{
printf("a[%d] = %d.\n", i, a[i]);
}

printf("=======================================================\n");
int j;

for (j = 9; j > 0; j --)
{
flag = 1;
for(i = 0; i < j; i ++)
{
if(a[i] > a[i+1])
{
int tmp;
tmp = a[i + 1];
a[i+1] = a[i];
a[i] = tmp;
flag = 0;
}
}
if(flag) break;
}

for(i = 0; i < 10; i ++)
{
printf("a[%d] = %d.\n", i, a[i]);
}

return 0;
}

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_算法_13

空间复杂度好说了,也就是TMP占用的空间,理想情况不需要交换,空间复杂度为0,最差的时候也仅仅是需要1个TMP的空间,也即是O(1).

关于循环条件的两种情况:

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_c++_14

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_数据结构_15

二分排序

一把撸成的二分排序。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int *binary_sort(int *array, int n)
{
int *p = malloc(n * sizeof(int));
if(p == NULL)
{
printf("%s line %d.\n", __func__, __LINE__);
return NULL;
}

memset(p, 0x00, sizeof(int)*n);

if (n == 1)
{
p[0] = array[0];
return p;
}

int n1,n2;

if(n % 2 == 0)
{
n1 = n2 = n/2;
}
else
{
n1 = n/2;
n2 = n1 + 1;
}

if((n1 + n2) != n)
{
printf("fatal error.\n");
return NULL;
}

int *pre = binary_sort(array, n1);
int *pos = binary_sort(array + n1, n2);

int i = 0;
int j = 0;
int s = 0;

int tmp;
for(s = 0; s < n; s ++)
{
if(i < n1 && j < n2)
{
if( pre[i] <= pos[j] )
{
tmp = pre[i];
i ++;
}
else
{
tmp = pos[j];
j ++;
}
}
else
{
if(i >= n1)
{
tmp = pos[j]; j ++;
}
else
{
tmp = pre[i]; i ++;
}
}

p[s] = tmp;
}

free(pre);
free(pos);
pre = pos = NULL;

return p;
}

#define NUM 800
int main(void)
{
int i;
int array[NUM];

for(i = 0; i < NUM; i ++)
{
array[i] = NUM-i;
}

for(i = 0; i < NUM; i ++)
{
printf("array[%d] = %d.\n", i, array[i]);
}

int *p = binary_sort(array, NUM);

for(i = 0; i < NUM; i ++)
{
printf("p[%d] = %d.\n", i, p[i]);
}

free(p);

return 0;
}

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_数据结构_16

归并排序复杂度分析:

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_#include_17

所以,复杂度为

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_#include_18

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_数组_19

全排列程序:

#include <stdio.h>
#include <stdlib.h>

#define NUMS 10

static int num[NUMS];
static int flag[NUMS];
static int res[NUMS];
static int depth = 0;
static long total = 0;

void total_pailie(void)
{
int i;

if(depth == NUMS)
{
int j = 0;
for (j = 0; j < NUMS ;j ++)
{
printf("%d ", res[j]);
}
total ++;
printf("\n");
}

for(i = 0; i < NUMS; i ++)
{
if(flag[i] == 1) continue;

flag[i] = 1;
res[depth] = num[i];
depth ++;
total_pailie();
depth --;
flag[i] = 0;
}
}

int main(void)
{
int i;

for(i = 0; i < NUMS; i ++)
{
num[i] = i;
flag[i] = 0;
res[i] = 0;
}

total_pailie();

printf("%s line %d, total %ld.\n", __func__, __LINE__, total);
return 0;
}

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_数据结构_20

10的阶乘:

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_算法_21

另一种方式,由于内部已经包含了排列组合公式,不是一个好方案,相当于手工代替机器做了很多额外的工作:

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_算法_22

如何判断一个链表是否存在循环?

快慢指针的方式,代码如下:

#include <stdio.h>
#include <stdlib.h>

struct node head;
struct node *phead;

struct node {
struct node *next;
int val;
};

void add_node(struct node *n)
{
struct node *no = phead;

while(no->next) no = no->next;

no->next = n;

return;
}

int check_loop(void)
{
int ret = 0;
struct node *n1, *n2;

n1 = n2 = phead;

while(n1 && n2)
{
n1 = n1->next;
if(!n1) {
ret = 0;
printf("exit in zero.\n");
break;
}
n2 = n2->next;
if(!n2) {
ret = 0;
printf("exit in one.\n");
break;
}
n2 = n2->next;
if(n1 == n2) {
ret = 1;
break;
}
}

if(!n1)
{
printf("n1 is null.\n");
}

if(!n2)
{
printf("n2 is null.\n");
}

printf("exit in two.\n");
return ret;
}

int main(void)
{
int i;
struct node array[100];
struct node *no;
phead = &head;

head.next = NULL;
head.val = 255;

for(i = 0; i < 100; i ++)
{
array[i].next = NULL;
array[i].val = i;
add_node(&array[i]);
}

no = phead;
while(no)
{
printf("%s line %d, val %d.\n", __func__, __LINE__, no->val);
no = no->next;
}

// add loop node.
add_node(&array[6]);

#if 0
no = phead;
while(no)
{
printf("%s line %d, val %d.\n", __func__, __LINE__, no->val);
no = no->next;
}
#endif

printf("%s line %d, checkloop %s.\n", __func__, __LINE__, check_loop() ? "have loop" : "no loop");

return 0;
}

数据结构与算法分析(排序,递归,链表)快速排序为什么要从基准数的对面开始移动?_数组_23

证明,假设当前1倍速节点的位置为(n+k),则二倍速节点的位置为(2n+s).则最差情况下,存在着(n+k)*(2n+s)步,使两个节点同时达到这里。得证。

结束!

举报

相关推荐

0 条评论