C语言常见数据结构与算法笔记

本文是对一些常见的简单的算法的介绍及实现,均由C语言实现,仅供参考。学习更多Python & GIS的相关知识,请移步公众号GeodataAnalysis

1 排序算法

1.1 交换类排序

1.1.1 冒泡排序

void bubble_sort(int arr[], int len) 
{
    
    
    int i, j, temp;
    for (i = 0; i < len - 1; i++)
        for (j = 0; j < len - 1 - i; j++)
            if (arr[j] > arr[j + 1]) 
	        {
    
    
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
}

1.1.2 快速排序

算法1
void QuickSort(int *arr,int low,int high)
{
    
    
    if(low >= high)
        return ;
    int k = arr[(low+high)/2];
    int temp;
    int i = low,j = high;
    while(i < j)
    {
    
    
        while(arr[j] > k)
            j--;
        while(arr[i] < k)
            i++;
        if(i <= j)
        {
    
    
            temp = arr[j];arr[j--] = arr[i];arr[i++] = temp;
        }
    }
    QuickSort(arr,low,j);
    QuickSort(arr,i,high);
}
算法2
void QuickSort(int *arr, int low, int high)
{
    
    
    if (low < high)
    {
    
    
        int i = low;
        int j = high;
        int k = arr[low];
        while (i < j)
        {
    
    
            while(i < j && arr[j] >= k)/*从右向左找第一个小于k的数*/
                j--;
            if(i < j)
                arr[i++] = arr[j];
            while(i < j && arr[i] < k)/*从左向右找第一个大于等于k的数*/
                i++;
            if(i < j)
                arr[j--] = arr[i];
        }
        arr[i] = k;
        QuickSort(arr, low, i - 1);     /*排序k左边*/
        QuickSort(arr, i + 1, high);    /*排序k右边*/
    }
}

1.2 插入类排序

1.2.1 简单插入排序

void insertion_sort(int arr[], int len)
{
    
    
    int i,j,temp;
    for (i=1;i<len;i++)
    {
    
    
        temp = arr[i];
        for (j=i;j>0 && arr[j-1]>temp;j--)
            arr[j] = arr[j-1];
        arr[j] = temp;
    }
}

1.2.2 希尔排序

void shell_sort(int arr[], int length) 
{
    
    
    int gap=length/2, i, j;
    int temp;
    while(gap>0)
    {
    
    
    	for(i=gap;i<len;i++)
    	{
    
    
    		temp = arr[i];
    		for(j=i-gap;j>=0 && temp<arr[j];j-=gap)
    		{
    
    
    			arr[j+gap] = arr[j];
			}
			arr[j+gap] = temp;
		}
		gap = gap/2;
	}
}

1.3 选择类排序

1.3.1 简单选择排序

void swap(int *a,int *b) //交換两个整数
{
    
    
	int temp = *a;
	*a = *b;
	*b = temp;
}
void selection_sort(int arr[], int len)
{
    
    
	int i,j;
	for (i = 0 ; i < len - 1 ; i++)
	{
    
    
		int min = i;
		for (j = i + 1; j < len; j++) //走访未排序的元素
			if (arr[j] < arr[min]) //找到目前最小值
				min = j; //记录最小值
        if(min != i)
			swap(&arr[min], &arr[i]); //做交换
	}
}

1.3.2 堆排序

void swap(int *a,int *b)//该函数用于交换两个变量的值
{
    
    
    int temp=*a;
    *a=*b;
    *b=temp;
}

void HeapAdjust1(int arr[],int first,int end)
{
    
    
	int parent = first;
	int temp = arr[parent],child;
	while((2*parent+1)<=end)
	{
    
    
		child = 2*parent+1;
		if(child<end && arr[child]<arr[child+1])
			child++;
		if(temp > arr[child])
			break;
		else
			arr[parent] = arr[child];
		parent = child;
	}
	arr[parent] = temp;
}

void HeapSort(int arr[],int length)
{
    
    
	int i,j;
	for(i=length/2;i>=0;i--)
	{
    
    
		HeapAdjust1(arr,i,length-1);
	}
	display(arr,L);
	for(j=length-1;j>0;j--)
	{
    
    
		swap(&arr[0],&arr[j]);
		HeapAdjust1(arr,0,j-1);
	}
}

2 递归算法

2.1 八皇后

include <stdio.h>

int count = 0;
int chess[8][8]={
    
    0};

int check(int row,int col)
{
    
    
	int i,j;
	
	//判断列方向 
	for(i=0;i<8;i++)
	{
    
    
		if(*(*(chess+i)+col))
			return 0;
	}
	
	//判断左对角线 
	for(i=row,j=col;i>=0 && j>=0;i--,j--)
	{
    
    
		if(*(*(chess+i)+j))
			return 0;
	}
	
	//判断右对角线 
	for(i=row,j=col;i>=0 && j<8;i--,j++)
	{
    
    
		if(*(*(chess+i)+j))
			return 0;
	}
	
	return 1;
}

void print()
{
    
    
	int i,j;
	count ++;
	printf("这是第%d种解法。\n",count);
	for(i=0;i<8;i++)
	{
    
    
		for(j=0;j<8;j++)
		{
    
    
			printf("%d ",*(*(chess+i)+j));
		}
		putchar('\n');
	}
}

void Eightqueen(int row)
{
    
    
	int col;
	
	if(row == 8)
	{
    
    
		print();
		return ;
	}
	else
	{
    
    
		for(col=0;col<8;col++)
		{
    
    
			if(check(row,col))//判断该位置是否可以放后 
			{
    
    
				*(*(chess+row)+col) = 1;
				Eightqueen(row+1);//可以放后,接着判断下一行 
				*(*(chess+row)+col) = 0;//回溯时将后清除 
			}
		}
	}
}

int main(int argc, char *argv[]) 
{
    
    
	Eightqueen(0);
	printf("\n共有%d种解法。\n",count);
	
	return 0;
}

2.2 汉诺塔问题

void hanoi(int n,char x,char y,char z)
{
    
    
	if(n>1)
	{
    
    
		hanoi(n-1,x,z,y);
		printf("%c->%c\n",x,z);
		hanoi(n-1,y,x,z);
	}
	else
		printf("%c->%c\n",x,z);
}

3 线性链表

struct Student
{
    
    
	int num;
	struct Student *next;
}*head, *students = (struct Student *)malloc(sizeof(struct Student));
students->next = NULL; students->num = 0;
head = students;

3.1 初始化线性链表

尾插法
void taillink(struct Student **head, int input)
{
    
    
	struct Student *student;
	static struct Student *tail;
	student = (struct Student *)malloc(sizeof(struct Student));
	if(student == NULL)
	{
    
    
		printf("内存分配失败了。。。");
		exit(1);
	}
	student->num = input;

	if((*head)->next != NULL)
	{
    
    
		tail->next = student;
		student->next = NULL;
	}
	else
	{
    
    
		(*head)->next = student;
		student->next = NULL;
	}
	tail = student;
    (*head)->num++;
}
头插法
void headlink(struct Student **head,int input)
{
    
    
	struct Student *student;
	student = (struct Student *)malloc(sizeof(struct Student));
	if(student == NULL)
	{
    
    
		printf("内存分配失败了。。。");
		exit(1);
	}
	student->num = input;

	if((*head)->next == NULL)
	{
    
    
		(*head)->next = student;
		student->next = NULL;
	}
	else
	{
    
    
		student->next = (*head)->next;
        (*head)->next = student;
	}
}

3.2 线性链表的查找

struct Student *find(struct Student *head,int n)
{
    
    
	struct Student *p = NULL;
	p = head->next;
	while(p != NULL)
	{
    
    
		if(p->num == n)
		{
    
    
			return p;
		}
		p = p->next;
	}
	return NULL;
}

3.3 线性链表的插入

void insert(struct Student **head, int n)
{
    
    
    struct Student *current = (*head)->next;
	struct Student *previous = *head;
	struct Student *student;
	student = (struct Student *)malloc(sizeof(struct Student));
	if(student == NULL)
	{
    
    
		printf("内存分配失败了。。。");
		exit(1);
	}
	student->num = n;
	if(!current)
    {
    
    
        previous->next = student;
        student->next = NULL;
        return ;
    }
    while(current && current->num < n)  //注意此处顺序不能反
    {
    
    
        previous = current;
        current = current->next;
    }
    if(!current)
    {
    
    
        previous->next = student;
        student->next = NULL;
    }
    else
    {
    
    
        previous->next = student;
        student->next = current;
    }
    (*head)->num++;  //线性链表长度自增1
}

3.4 线性链表的释放

void freelink(struct Student *head)
{
    
    
    struct Student *current, *next;
    current = head->next;
    while(!current)
    {
    
    
        next = current->next;
        free(current);
        current = next;
    }
}

3.5 线性链表的输出

void freelink(struct Student *head)
{
    
    
    struct Student *current, *next;
    current = head->next;
    while(!current)
    {
    
    
        next = current->next;
        free(current);
        current = next;
    }
}

3.6 线性链表的删除

void del(struct Student **head, int n)
{
    
    
    struct Student *current = (*head)->next;
	struct Student *previous = *head;
	if(!current)
    {
    
    
        printf("Not Found!!!\n");
        return ;
    }
    while(current && current->num != n)  //注意此处顺序不能反
    {
    
    
        previous = current;
        current = current->next;
    }
    if(!current)
    {
    
    
        printf("Not Found!!!\n");
        return ;
    }
    else
    {
    
    
        previous->next = current->next;
        free(current);
    }
    (*head)->num--;  //线性链表长度自减1
}

3.7 线性链表的合并

void merge(struct Student **Library1, struct Student **Library2)
{
    
    
	struct Student *temp;
	temp = *Library1;
	while(temp != NULL)
	{
    
    
		if(temp->next == NULL)
		{
    
    
		    if((*Library2))
                temp->next = (*Library2)->next;
			return ;
		}
		temp = temp->next;
	}
}

4 字符串处理

4.1 BF算法

int find(char parent[],char child[])
{
    
    
	int i=0,j=0,len=0;
	while(*(child+(len++)) != '\0');
	printf("%d\n",len);
	while(parent[i] != '\0')
	{
    
    
		if(parent[i] == child[j])
		{
    
    
			j = 0;
			while(child[j++] != '\0' && child[j-1] == parent[i+(j-1)]);
			if(j == len)
				return i+1;
			j=0;
		}
		i++;
	}
	return 0;
}

4.2 KMP算法

#include <Stdio.h>

void print(char array[])
{
    
    
    for(int i=0; array[i] != '\0'; i++)
        printf("%c ", array[i]);
    putchar('\n');
}

void print(int array[], int len)
{
    
    
    for(int i=0; i<len+1; i++)
        printf("%d ", array[i]);
    putchar('\n');
}

int get_len(char array[])
{
    
    
    int len = 0;
    for(int i=1; array[i] != '\0'; i++)
        len++;
    return len;
}

//KMP算法
void get_next(char array[], int next[], int len)
{
    
    
    int i=0, j=1;
    next[1] = 0;
    while(j < len)
    {
    
    
        if(0==i || array[i]==array[j])
        {
    
    
            i++; j++;
            if(array[i] != array[j])
                next[j] = i;
            else
                next[j] = next[i];
        }
        else
        {
    
    
            i = next[i];
        }
    }
}

int kmp_find(char parent[], char child[])
{
    
    
    int i=1, j=1;
    int parent_len = get_len(parent), child_len = get_len(child);
    int next[100] = {
    
    0};

    get_next(child, next, child_len);
    print(parent);
    print(child);
    print(next, child_len);

    while(i<=parent_len && j<=child_len)
    {
    
    
        if(0==j || parent[i]==child[j])
        {
    
    
            i++; j++;
        }
        else
        {
    
    
            j = next[j];
        }
    }
    if(j > child_len)
        return i-child_len;
    else
        return 0;
}

int main(int argc, char *argv[])
{
    
    
    char parent[100]={
    
    " ffffishcbbb"}, child[100]={
    
    " ffffffishc"};

    printf("%d", kmp_find(parent, child));

    return 0;
}

5 二叉树的建立及遍历算法

5.1 一般二叉树

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

typedef struct TreeNode
{
    
    
    char data;
    struct TreeNode *lchild, *rchild;
}*ptrTreeNode, TreeNode;

void createTree(ptrTreeNode *r)
{
    
    
    char c;

    scanf("%c", &c);
    if('#' == c)
        *r = NULL;
    else
    {
    
    
        *r = (ptrTreeNode)malloc(sizeof(TreeNode));
        (*r)->data = c;
        createTree(&(*r)->lchild);
        createTree(&(*r)->rchild);
    }
}

void visit(char data, int level)
{
    
    
    printf("结点%c在第%d层\n", data, level);
}

void preOrderTra(ptrTreeNode r, int level)
{
    
    
    if(r)
    {
    
    
        visit(r->data, level);
        preOrderTra(r->lchild, level+1);
        preOrderTra(r->rchild, level+1);
    }
}

void freeTree(ptrTreeNode r)
{
    
    
    if(r)
    {
    
    
        freeTree(r->lchild);
        freeTree(r->rchild);
        printf("释放结点%c\n", r->data);
        free(r);
    }
}


int main(int argc, char *argv[])
{
    
    
    ptrTreeNode root;

    createTree(&root);
    preOrderTra(root, 1);
    freeTree(root);

    return 0;
}

AB#D##CE###
结点A在第1层
结点B在第2层
结点D在第3层
结点C在第2层
结点E在第3层
释放结点D
释放结点B
释放结点E
释放结点C
释放结点A

5.2 线索二叉树

一、线索二叉树的原理

通过考察各种二叉链表,不管二叉树的形态如何,空链域的个数总是多过非空链域的个数。准确的说,n个结点的二叉链表共有2n个链域,非空链域为n-1个,但其中的空链域却有n+1个。如下图所示。

img

因此,提出了一种方法,利用原来的空链域存放指针,指向树中其他结点。这种指针称为线索。记ptr指向二叉链表中的一个结点,以下是建立线索的规则:

  • 如果ptr->lchild为空,则存放指向中序遍历序列中该结点的前驱结点。这个结点称为ptr的中序前驱;
  • 如果ptr->rchild为空,则存放指向中序遍历序列中该结点的后继结点。这个结点称为ptr的中序后继。

显然,在决定lchild是指向左孩子还是前驱,rchild是指向右孩子还是后继,需要一个区分标志的。因此,我们在每个结点再增设两个标志域ltagrtag,注意ltagrtag只是区分0或1数字的布尔型变量,其占用内存空间要小于像lchildrchild的指针变量。结点结构如下所示。

img

其中:

  • ltag为0时指向该结点的左孩子,为1时指向该结点的前驱;
  • rtag为0时指向该结点的右孩子,为1时指向该结点的后继。

因此对于上图的二叉链表图可以修改为下图的样子:

img

二、线索二叉树的实现

二叉树的存储结构定义如下:

typedef enum{
    
    Link, Thread} PointerTag;      //link = 0表示指向左右孩子指针
                                            //Thread = 1表示指向前驱或后继的线索
typedef struct BitNode
{
    
    
    char data;                              //结点数据
    struct BitNode *lchild;                 //左右孩子指针
    struct BitNode *rchild;
    PointerTag ltag;                        //左右标志
    PointerTag rtag;
}BitNode, *BiTree;

BiTree pre;                                 //全局变量,始终指向刚刚访问过的结点

二叉树的采用前序遍历的方式创建,如下:

//前序创建二叉树
void CreateTree(BiTree *t)
{
    
    
    char ch;
    scanf("%c", &ch);

    if(ch == '#')
        *t = NULL;
    else
    {
    
    
        (*t) = (BiTree)malloc(sizeof(BitNode));

        (*t)->data = ch;
        (*t)->ltag = Link;
        (*t)->rtag = Link;
        CreateTree(&((*t)->lchild));
        CreateTree(&((*t)->rchild));
    }
}

线索化的实质就是将二叉链表中的空指针改为指向前驱或后继的线索。由于前驱和后继信息只有在遍历该二叉树时才能得到,所以,线索化的过程就是在遍历的过程中修改空指针的过程。中序遍历线索化的递归函数代码如下:

//中序遍历进行中序线索化
void InThreading(BiTree p)
{
    
    
    if(p)
    {
    
    
        InThreading(p->lchild);              //递归左子树线索化
        
        if(!p->lchild)                       //没有左孩子
        {
    
    
            p->ltag = Thread;                //前驱线索
            p->lchild = pre;             //左孩子指针指向前驱
        }
        if(!pre->rchild)                 //前驱没有右孩子
        {
    
    
            pre->rtag = Thread;              //为前驱添加后继线索
            pre->rchild = p;             //前驱右孩子指针指向后继(当前结点p)
        }
        pre = p;			//记录前驱结点的指针指向当前结点

        InThreading(p->rchild);              //递归右子树线索化
    }
}

中间部分代码做了这样的事情:

因为此时p结点的后继还没有访问到,因此只能对它的前驱结点pre的右指针rchild做判断,if(!pre->rchild)表示如果为空,则p就是pre的后继,于是pre->rchild = p,并且设置pre->rtag = Thread,完成后继结点的线索化。如图:

img

if(!p->lchild)表示如果某结点的左指针域为空,因为其前驱结点刚刚访问过,赋值了pre,所以可以将pre赋值给p->lchild,并修改p->ltag = Thread(也就是定义为1)以完成前驱结点的线索化。

img

完成前驱和后继的判断后,不要忘记将当前结点p赋值给pre,以便于下一次使用。有了线索二叉树后,对它进行遍历时,其实就等于操作一个双向链表结构。

img

和双向链表结点一样,在二叉树链表上添加一个头结点,如下图所示,并令其lchild域的指针指向二叉树的根结点(下图第一步),其rchild域的指针指向中序遍历访问时的最后一个结点(下图第二步)。反之,令二叉树的中序序列中第一个结点的lchild域指针和最后一个结点的rchild域指针均指向头结点(下图第三和第四步)。这样的好处是:我们既可以从第一个结点起顺后继进行遍历,也可以从最后一个结点起顺前驱进行遍历。此外,可以令pre指向头结点,并随着线索化的过程不断改变指向,使其始终指向正在进行线索化的结点的前驱。

img

//建立头结点,中序线索二叉树
void InOrderThread_Head(BiTree *h, BiTree t)
{
    
    
    //   为头指针分配空间,头指针的左孩子必然指向根结点,
    //右孩子必然指向中序遍历的最后一个结点,故将ltag设为
    //Link,rtag设为Thread,右孩子暂时指向本身
    (*h) = (BiTree)malloc(sizeof(BitNode));
    (*h)->ltag = Link;
    (*h)->rchild = *h;
    (*h)->rtag = Thread;

    if(!t)//如果根结点为NULL,即为空树,将头指针左右孩子都指向本身
        (*h)->lchild = *h;
    else
    {
    
    
        (*h)->lchild = t;        //头指针指向根结点
        pre = *h;       //pre指向头指针
        InThreading(t);         //线索化
        //线索化后pre指向中序遍历的最后一个结点,故
        //将其右孩子指向头指针,rtag设为Thread
        pre->rchild = *h;
        pre->rtag = Thread;
        //最后将头指针的右孩子指向中序遍历的最后一个结点
        (*h)->rchild = pre;
    }
}

采用迭代进行中序遍历代码如下:

//t指向头结点,头结点左链lchild指向根结点,头结点右链rchild指向中序遍历的最后一个结点。
//迭代法中序遍历二叉线索树表示的二叉树t
void InOrderThraverse_Thr(BiTree t)
{
    
    
    BiTree p;
    p = t->lchild;           //p指向根结点
    while(p != t)
    {
    
    
        while(p->ltag == Link)   //当ltag = 0时循环到中序序列的第一个结点
            p = p->lchild;
        printf("%c ", p->data);  //显示结点数据,可以更改为其他对结点的操作

        while(p->rtag == Thread && p->rchild != t)
        {
    
    
            p = p->rchild;
            printf("%c ", p->data);
        }

        p = p->rchild;           //p进入其右子树
    }
}

最终测试如下:

int main(int argc, char **argv)
{
    
    
    BiTree root;
    BiTree head;

    printf("请输入前序二叉树的内容:\n");
    CreateTree(&root);                 //建立二叉树
    InOrderThread_Head(&head, root);       //加入头结点,并线索化
    printf("输出中序二叉树的内容:\n");
    InOrderThraverse_Thr(head);

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

请输入前序二叉树的内容:
ABDH##I##EJ###CF##G##
输出中序二叉树的内容:
H D I B J E A F C G

参考文章:

https://www.cnblogs.com/lishanlei/p/10707834.html

http://blog.chinaunix.net/uid-26548237-id-3476920.html

猜你喜欢

转载自blog.csdn.net/weixin_44785184/article/details/128750375