├── README.md ├── heapSort └── heapSort.c ├── bitmap └── bitmap.c ├── queue ├── arrayQueue.c └── 队列-C语言实现.md ├── stack ├── arrayStack.c ├── linkStack.c ├── leetcode题解-20.有效的括号.md ├── expressionEvaluation.c ├── expression Evaluation.md ├── expressionEvaluation.md └── 栈-c语言实现.md ├── sort ├── quicksort.c └── 快速排序.md ├── tree ├── binarySearchTree.c └── traversal.c ├── priorityQueue └── priorityQueue.c └── binarySearch └── findNotExistOne.c /README.md: -------------------------------------------------------------------------------- 1 | # data-structures-and-algorithms-in-c 2 | 数据结构与算法:C语言描述 3 | 4 | 常见数据结构与算法C语言实现 5 | 6 | * [栈](#栈) 7 | * [队列](#队列) 8 | * [树](#树) 9 | * [优先队列](#优先队列) 10 | * [散列表](#散列表) 11 | * [排序](#排序) 12 | * [二分法](#二分法) 13 | * [位图法](#位图法) 14 | 15 | ## 栈 16 | #### [栈](stack) 17 | 18 | + [栈-C语言实现(文)](https://www.yanbinghu.com/2019/03/16/31765.html) 19 | + [leetcode题解-20.有效的括号(文)](https://www.yanbinghu.com/2019/03/17/62434.html) 20 | + [链式栈](stack/linkStack.c) 21 | + [数组栈](stack/arrayStack.c) 22 | + [利用栈实现表达式求值(文)](https://www.yanbinghu.com/2019/03/24/57779.html) 23 | 24 | 25 | ## 队列 26 | 27 | #### [队列](queue) 28 | + [队列-C语言实现(文)](https://www.yanbinghu.com/2019/03/28/13055.html) 29 | + [数组实现队列](queue/arrayQueue.c) 30 | 31 | ## 树 32 | #### [树](tree) 33 | 34 | + [二叉查找树(文)](https://www.yanbinghu.com/2019/04/07/55964.html) 35 | + [二叉树遍历(文)](https://www.yanbinghu.com/2019/04/11/63105.html) 36 | + [二叉树遍历](tree/traversal.c) 37 | + [二叉树插入查找删除](tree/binarySearchTree.c) 38 | 39 | ## 优先队列 40 | 41 | #### [优先队列](heap) 42 | 43 | + [优先队列(文)](https://www.yanbinghu.com/2019/05/17/36705.html) 44 | + [优先队列](priorityQueue/priorityQueue.c) 45 | 46 | ## 散列表 47 | 48 | #### [散列表](hashTable) 49 | 50 | + [什么是散列表(文)](https://www.yanbinghu.com/2019/05/04/61373.html) 51 | 52 | ## 排序 53 | 54 | #### [排序](sort) 55 | 56 | + [快速排序(文)](https://www.yanbinghu.com/2019/02/21/28355.html) 57 | + [快速排序递归法](sort/quicksort.c) 58 | + [堆排序](heapSort/heapSort.c) 59 | 60 | ## 二分法 61 | 62 | #### [二分法](binarySearch) 63 | 64 | + [从40亿个整数中找到不存在的一个(文)](https://www.yanbinghu.com/2018/12/25/10757.html) 65 | + [从40亿个整数中找到不存在的一个](binarySearch/findNotExistOne.c) 66 | 67 | ## 位图法 68 | 69 | #### [位图法](bitmap) 70 | 71 | + [对一千万整数快速排序](https://www.yanbinghu.com/2018/12/08/34927.html) 72 | + [bitmap](./bitmap/bitmap.c) 73 | -------------------------------------------------------------------------------- /heapSort/heapSort.c: -------------------------------------------------------------------------------- 1 | #include 2 | typedef int ElementType; 3 | void swap(ElementType *a,ElementType *b) 4 | { 5 | ElementType temp = *a; 6 | *a = *b; 7 | *b = temp; 8 | } 9 | 10 | /*打印数组内容*/ 11 | void printArr(ElementType arr[],int len) 12 | { 13 | if(NULL == arr) 14 | return; 15 | int i = 0 ; 16 | while(i < len) 17 | { 18 | printf("%d ",arr[i]); 19 | i++; 20 | } 21 | printf("\n"); 22 | } 23 | 24 | /*调整位置i的元素到合适的位置 25 | 父节点i 26 | 左孩子节点2 * i + 1 27 | 右孩子节点2 * i + 2 28 | */ 29 | void adjust_ele(ElementType arr[],int i,int length) 30 | { 31 | int child; 32 | ElementType temp; 33 | for(temp = arr[i];2*i+1 < length;i = child) 34 | { 35 | child = 2 * i +1; 36 | 37 | /*找到较大的儿子*/ 38 | if(child != length-1 && arr[child+1] > arr[child]) 39 | child+=1; 40 | /*如果空穴元素小于该儿子,则空穴下滑*/ 41 | if(temp < arr[child]) 42 | arr[i] = arr[child]; 43 | else 44 | break; 45 | } 46 | /*将i位置的元素放到正确的位置*/ 47 | arr[i] = temp; 48 | } 49 | void heap_sort(ElementType arr[],int length) 50 | { 51 | int i = 0; 52 | /*构建堆*/ 53 | for(i = length /2;i >= 0;i--) 54 | { 55 | adjust_ele(arr,i,length); 56 | //printArr(arr,length); 57 | } 58 | for(i = length-1;i > 0;i--) 59 | { 60 | /*填充i位置的空穴*/ 61 | swap(&arr[0],&arr[i]); 62 | 63 | /*每次都处理堆顶元素*/ 64 | adjust_ele(arr,0,i); 65 | //printArr(arr,length); 66 | } 67 | } 68 | 69 | int main(void) 70 | { 71 | /*创建数组*/ 72 | ElementType arr[] = {1,10,8,5,7,15,35}; 73 | int len = sizeof(arr)/sizeof(ElementType); 74 | printf("before sort:"); 75 | printArr(arr,len); 76 | 77 | heap_sort(arr,len); 78 | printf("after sort:"); 79 | printArr(arr,len); 80 | 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /bitmap/bitmap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #define CHAR_BIT 8 // char类型占用的bit位数 4 | #define SHIFT 3 //右移的位数 5 | #define MAX_NUM 10000000/8 6 | #define BIT_SIZE 10000000 //所需比特位总数量 7 | #define MAX_STR 10 //一个整数所需最大字符数 8 | #define INPUT_FILE "srcNum.txt" 9 | #define OUTPUT_FILE "dstNum.txt" 10 | /*将整数对应的比特位置1*/ 11 | int putIntoBitMap(char *bitmap, int num) 12 | { 13 | int byte = num >> SHIFT; 14 | char bit = 1 << num % CHAR_BIT; 15 | bitmap[byte] |= (char) bit; 16 | return 0; 17 | } 18 | /*判断整数是否在位图中*/ 19 | int isInBitMap(char *bitmap, int num) 20 | { 21 | int byte = num >> SHIFT; 22 | char bit = 1 << num % CHAR_BIT; 23 | if (bitmap[byte] & (char) bit) 24 | return 1; 25 | else 26 | return 0; 27 | } 28 | int main(void) 29 | { 30 | /*打开源文件*/ 31 | FILE *in = fopen( INPUT_FILE, "r" ); 32 | if(NULL == in) 33 | { 34 | printf("open src num failed"); 35 | return -1; 36 | } 37 | 38 | /*申请位图相关内存,并初始化为0*/ 39 | char string[MAX_STR] = { 0 }; 40 | char *bitmap = (char*)calloc(MAX_NUM,sizeof(char)); 41 | if(NULL == bitmap) 42 | { 43 | fclose(in); 44 | return -1; 45 | } 46 | int num = 0; 47 | /*循环读取文件中的整数,并将对应位置1*/ 48 | while(fgets(string, MAX_STR, in ) != NULL) 49 | { 50 | num = atoi(string); 51 | putIntoBitMap(bitmap, num); 52 | //printf("%d\n",num); 53 | } 54 | fclose(in); 55 | /*遍历位图中的比特位,为1,则输出整数到文件中*/ 56 | FILE *out = fopen(OUTPUT_FILE, "w+"); 57 | if(NULL == out) 58 | { 59 | printf("open dst num failed"); 60 | free(bitmap); 61 | bitmap = NULL; 62 | return -1; 63 | } 64 | int i; 65 | for (i = 0; i < BIT_SIZE; i++) 66 | { 67 | if (isInBitMap(bitmap , i)) 68 | { 69 | fprintf(out, "%d\n", i); 70 | //printf("%d\n",i); 71 | } 72 | } 73 | fclose(out); 74 | free(bitmap); 75 | bitmap = NULL; 76 | return 0; 77 | } 78 | -------------------------------------------------------------------------------- /queue/arrayQueue.c: -------------------------------------------------------------------------------- 1 | //arrayQueue.c 2 | #include 3 | #include 4 | typedef int ElementType; 5 | 6 | /*为测试,将容量值定为较小值5*/ 7 | #define MAX_SIZE 5 8 | 9 | /*定义队列结构*/ 10 | typedef struct QueueInfo 11 | { 12 | int front; //队头位置 13 | int rear; //队尾位置 14 | ElementType queueArr[MAX_SIZE];//队列数组 15 | }QueueInfo; 16 | 17 | #define SUCCESS 0 18 | #define FAILURE 1 19 | #define FALSE 0 20 | #define TRUE 1 21 | 22 | /*判断队列是否已满*/ 23 | int queue_is_full(QueueInfo *queue) 24 | { 25 | if((queue->rear + 2) % MAX_SIZE == queue->front) 26 | { 27 | printf("queue is full\n"); 28 | return TRUE; 29 | } 30 | else 31 | return FALSE; 32 | } 33 | /*判断队列是否为满*/ 34 | int queue_is_empty(QueueInfo *queue) 35 | { 36 | if((queue->rear + 1) % MAX_SIZE == queue->front) 37 | { 38 | printf("queue is empty\n"); 39 | return TRUE; 40 | } 41 | else 42 | return FALSE; 43 | } 44 | 45 | /*出队*/ 46 | int queue_delete(QueueInfo *queue,ElementType *value) 47 | { 48 | if(queue_is_empty(queue)) 49 | return FAILURE; 50 | 51 | *value = queue->queueArr[queue->front]; 52 | printf("get value from front %d is %d\n",queue->front,*value); 53 | queue->front = (queue->front + 1) % MAX_SIZE; 54 | return SUCCESS; 55 | } 56 | 57 | /*入队*/ 58 | int queue_insert(QueueInfo *queue,ElementType value) 59 | { 60 | if(queue_is_full(queue)) 61 | return FAILURE; 62 | 63 | queue->rear = (queue->rear + 1) % MAX_SIZE; 64 | queue->queueArr[queue->rear] = value; 65 | printf("insert %d to %d\n",value,queue->rear); 66 | return SUCCESS; 67 | } 68 | int main(void) 69 | { 70 | /*队列初始化*/ 71 | QueueInfo queue; 72 | memset(&queue,0,sizeof(queue)); 73 | queue.front = 1; 74 | queue.rear = 0; 75 | 76 | /*入队6个数据,最后两个入队失败*/ 77 | queue_insert(&queue,5); 78 | queue_insert(&queue,4); 79 | queue_insert(&queue,3); 80 | queue_insert(&queue,2); 81 | queue_insert(&queue,1); 82 | queue_insert(&queue,0); 83 | 84 | /*出队6个数据,最后两个出队失败*/ 85 | ElementType a = 0; 86 | queue_delete(&queue,&a); 87 | queue_delete(&queue,&a); 88 | queue_delete(&queue,&a); 89 | queue_delete(&queue,&a); 90 | queue_delete(&queue,&a); 91 | queue_delete(&queue,&a); 92 | return 0; 93 | } 94 | -------------------------------------------------------------------------------- /stack/arrayStack.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define STACK_SIZE 64 /*栈大小*/ 4 | #define TOP_OF_STACK -1 /*栈顶位置*/ 5 | typedef int ElementType; /*栈元素类型*/ 6 | 7 | #define SUCCESS 0 8 | #define FAILURE -1 9 | 10 | /*定义栈结构*/ 11 | typedef struct StackInfo 12 | { 13 | int topOfStack; /*记录栈顶位置*/ 14 | ElementType stack[STACK_SIZE]; /*栈数组,也可以使用动态数组实现*/ 15 | }StackInfo_st; 16 | 17 | 18 | /*函数声明*/ 19 | int stack_push(StackInfo_st *s,ElementType value); 20 | int stack_pop(StackInfo_st *s,ElementType *value); 21 | int stack_top(StackInfo_st *s,ElementType *value); 22 | int stack_is_full(StackInfo_st *s); 23 | int stack_is_empty(StackInfo_st *s); 24 | 25 | 26 | /*入栈,0表示成,非0表示出错*/ 27 | int stack_push(StackInfo_st *s,ElementType value) 28 | { 29 | if(stack_is_full(s)) 30 | return FAILURE; 31 | /*先增加topOfStack,再赋值*/ 32 | s->topOfStack++; 33 | s->stack[s->topOfStack] = value; 34 | return SUCCESS; 35 | } 36 | 37 | /*出栈*/ 38 | int stack_pop(StackInfo_st *s,ElementType *value) 39 | { 40 | /*首先判断栈是否为空*/ 41 | if(stack_is_empty(s)) 42 | return FAILURE; 43 | *value = s->stack[s->topOfStack]; 44 | s->topOfStack--; 45 | return SUCCESS; 46 | } 47 | /*访问栈顶元素*/ 48 | int stack_top(StackInfo_st *s,ElementType *value) 49 | { 50 | /*首先判断栈是否为空*/ 51 | if(stack_is_empty(s)) 52 | return FAILURE; 53 | *value = s->stack[s->topOfStack]; 54 | return SUCCESS; 55 | } 56 | 57 | /*判断栈是否已满,满返回1,未满返回0*/ 58 | int stack_is_full(StackInfo_st *s) 59 | { 60 | return s->topOfStack == STACK_SIZE - 1; 61 | } 62 | /*判断栈是否为空,空返回1,非空返回0*/ 63 | int stack_is_empty(StackInfo_st *s) 64 | { 65 | return s->topOfStack == - 1; 66 | } 67 | int main(void) 68 | { 69 | 70 | /*创建栈*/ 71 | StackInfo_st stack; 72 | stack.topOfStack = TOP_OF_STACK; 73 | 74 | /*如果栈为空,则压入元素1*/ 75 | if(stack_is_empty(&stack)) 76 | { 77 | printf("push value 1\n"); 78 | stack_push(&stack,1); 79 | } 80 | 81 | /*访问栈顶元素*/ 82 | int topVal; 83 | stack_top(&stack, &topVal); 84 | printf("top value %d\n",topVal); 85 | 86 | /*出栈*/ 87 | int popVal; 88 | stack_pop(&stack, &popVal); 89 | printf("pop value %d\n",popVal); 90 | int i = 0; 91 | while(SUCCESS == stack_push(&stack,i)) 92 | { 93 | i++; 94 | } 95 | printf("stack is full,topOfStack is %d\n",stack.topOfStack); 96 | 97 | return 0; 98 | } 99 | -------------------------------------------------------------------------------- /stack/linkStack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | typedef int ElementType; /*栈元素类型*/ 4 | 5 | #define SUCCESS 0 6 | #define FAILURE -1 7 | 8 | /*定义栈结构*/ 9 | typedef struct StackInfo 10 | { 11 | ElementType value; /*记录栈顶位置*/ 12 | struct StackInfo *next; /*指向栈的下一个元素*/ 13 | }StackInfo_st; 14 | 15 | 16 | /*函数声明*/ 17 | StackInfo_st *createStack(void); 18 | int stack_push(StackInfo_st *s,ElementType value); 19 | int stack_pop(StackInfo_st *s,ElementType *value); 20 | int stack_top(StackInfo_st *s,ElementType *value); 21 | int stack_is_empty(StackInfo_st *s); 22 | 23 | /*创建栈,外部释放内存*/ 24 | StackInfo_st *createStack(void) 25 | { 26 | StackInfo_st *stack = malloc(sizeof(StackInfo_st)); 27 | if(NULL == stack) 28 | { 29 | printf("malloc failed\n"); 30 | return NULL; 31 | } 32 | stack->next = NULL; 33 | return stack; 34 | } 35 | /*入栈,0表示成,非0表示出错*/ 36 | int stack_push(StackInfo_st *s,ElementType value) 37 | { 38 | StackInfo_st *temp = malloc(sizeof(StackInfo_st)); 39 | if(NULL == temp) 40 | { 41 | printf("malloc failed\n"); 42 | return FAILURE; 43 | } 44 | /*将新的节点添加s->next前,使得s->next永远指向栈顶*/ 45 | temp->value = value; 46 | temp->next = s->next; 47 | s->next = temp; 48 | return SUCCESS; 49 | } 50 | 51 | /*出栈*/ 52 | int stack_pop(StackInfo_st *s,ElementType *value) 53 | { 54 | /*首先判断栈是否为空*/ 55 | if(stack_is_empty(s)) 56 | return FAILURE; 57 | 58 | /*找出栈顶元素*/ 59 | *value = s->next->value; 60 | StackInfo_st *temp = s->next; 61 | s->next = s->next->next; 62 | 63 | /*释放栈顶节点内存*/ 64 | free(temp); 65 | temp = NULL; 66 | 67 | return SUCCESS; 68 | } 69 | /*访问栈顶元素*/ 70 | int stack_top(StackInfo_st *s,ElementType *value) 71 | { 72 | /*首先判断栈是否为空*/ 73 | if(stack_is_empty(s)) 74 | return FAILURE; 75 | *value = s->next->value; 76 | return SUCCESS; 77 | } 78 | 79 | /*判断栈是否为空,空返回1,未空返回0*/ 80 | int stack_is_empty(StackInfo_st *s) 81 | { 82 | /*栈顶指针为空,则栈为空*/ 83 | return s->next == NULL; 84 | } 85 | 86 | int main(void) 87 | { 88 | 89 | /*创建栈*/ 90 | StackInfo_st *stack = createStack(); 91 | 92 | /*如果栈为空,则压入元素1*/ 93 | if(stack_is_empty(stack)) 94 | { 95 | printf("push value 1\n"); 96 | stack_push(stack,1); 97 | } 98 | 99 | /*访问栈顶元素*/ 100 | int topVal; 101 | stack_top(stack, &topVal); 102 | printf("top value %d\n",topVal); 103 | 104 | /*出栈*/ 105 | int popVal; 106 | stack_pop(stack, &popVal); 107 | printf("pop value %d\n",popVal); 108 | int i = 0; 109 | while(SUCCESS == stack_push(stack,i) && i < 5) 110 | { 111 | i++; 112 | } 113 | printf("top if stack value is %d\n",stack->next->value); 114 | /*最后记得将栈内存都释放,可自己尝试实现*/ 115 | return 0; 116 | } 117 | -------------------------------------------------------------------------------- /sort/quicksort.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | /*使用快速排序的区间大小临界值,可根据实际情况选择*/ 5 | #define MAX_THRESH 4 6 | typedef int ElementType; 7 | /*元素交换*/ 8 | void swap(ElementType *a,ElementType *b) 9 | { 10 | ElementType temp = *a; 11 | *a = *b; 12 | *b = temp; 13 | } 14 | /*插入排序*/ 15 | void insertSort(ElementType A[],int N) 16 | { 17 | /*优化后的插入排序*/ 18 | int j = 0; 19 | int p = 0; 20 | int temp = 0; 21 | for(p = 1;p0 && A[j-1] > temp;j--) 25 | { 26 | A[j] = A[j-1]; 27 | } 28 | A[j] = temp; 29 | } 30 | 31 | } 32 | /*三数中值法选择基准*/ 33 | ElementType medianPivot(ElementType A[],int left,int right) 34 | { 35 | int center = (left + right)/2 ; 36 | /*对三数进行排序*/ 37 | if(A[left] > A[center]) 38 | swap(&A[left],&A[center]); 39 | if(A[left] > A[right]) 40 | swap(&A[left],&A[right]); 41 | if(A[center] > A[right]) 42 | swap(&A[center],&A[right]); 43 | 44 | /*交换中值和倒数第二个元素*/ 45 | swap(&A[center],&A[right-1]); 46 | return A[right-1]; 47 | } 48 | 49 | /*分区操作*/ 50 | int partition(ElementType A[],int left,int right) 51 | { 52 | 53 | int i = left; 54 | int j = right-1; 55 | /*获取基准值*/ 56 | ElementType pivot = medianPivot(A,left,right); 57 | for(;;) 58 | { 59 | /*i j分别向右和向左移动,为什么不直接先i++?*/ 60 | while(A[++i] < pivot) 61 | {} 62 | while(A[--j] > pivot) 63 | {} 64 | 65 | if(i < j) 66 | { 67 | swap(&A[i],&A[j]); 68 | } 69 | /*交错时停止*/ 70 | else 71 | { 72 | break; 73 | } 74 | 75 | } 76 | /*交换基准元素和i指向的元素*/ 77 | swap(&A[i],&A[right-1]); 78 | return i; 79 | 80 | } 81 | 82 | 83 | void Qsort(ElementType A[],int left,int right) 84 | { 85 | int i = 0; 86 | register ElementType *arr = A; 87 | if(right-left >= MAX_THRESH) 88 | { 89 | /*分割操作*/ 90 | i = partition(arr,left,right); 91 | /*递归*/ 92 | Qsort(arr,left,i-1); 93 | Qsort(arr,i+1,right); 94 | } 95 | else 96 | { 97 | /*数据量较小时,使用插入排序*/ 98 | insertSort(arr+left,right-left+1); 99 | } 100 | } 101 | /*打印数组内容*/ 102 | void printArray(ElementType A[],int n) 103 | { 104 | if(n > 100) 105 | { 106 | printf("too much,will not print\n"); 107 | return; 108 | } 109 | int i = 0; 110 | while(i < n) 111 | { 112 | printf("%d ",A[i]); 113 | i++; 114 | } 115 | printf("\n"); 116 | } 117 | int main(int argc,char *argv[]) 118 | { 119 | if(argc < 2) 120 | { 121 | printf("usage:qsort num\n"); 122 | return -1; 123 | } 124 | int len = atoi(argv[1]); 125 | printf("sort for %d numbers\n",len); 126 | /*随机产生输入数量的数据*/ 127 | int *A = malloc(sizeof(int)*len); 128 | int i = 0; 129 | srand(time(NULL)); 130 | while(i < len) 131 | { 132 | A[i] = rand(); 133 | i++; 134 | } 135 | printf("before sort:"); 136 | printArray(A,len); 137 | /*对数据进行排序*/ 138 | Qsort(A,0,len-1); 139 | printf("after sort:"); 140 | printArray(A,len); 141 | return 0; 142 | } 143 | -------------------------------------------------------------------------------- /stack/leetcode题解-20.有效的括号.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: leetcode题解-20.有效的括号 3 | comments: true 4 | categories: leetcode 5 | tags: 6 | - leetcode 7 | abbrlink: 62434 8 | date: 2019-03-17 15:20:00 9 | --- 10 | ## 题目 11 | leetcode 20. 有效的括号 堆栈 12 | 13 | ## 有效的括号 14 | 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 15 | 16 | 有效字符串需满足: 17 | 18 | 左括号必须用相同类型的右括号闭合。 19 | 左括号必须以正确的顺序闭合。 20 | 注意空字符串可被认为是有效字符串。 21 | 22 | 示例 1: 23 | 24 | 输入: "()" 25 | 输出: true 26 | 示例 2: 27 | 28 | 输入: "()[]{}" 29 | 输出: true 30 | 示例 3: 31 | 32 | 输入: "(]" 33 | 输出: false 34 | 示例 4: 35 | 36 | 输入: "([)]" 37 | 输出: false 38 | 示例 5: 39 | 40 | 输入: "{[]}" 41 | 输出: true 42 | 43 | ## 解决方法 44 | 这道题就非常适合用我们之前介绍过的栈([栈-C语言实现](https://www.yanbinghu.com/2019/03/16/31765.html))这种数据结构来解决。怎么处理呢?我们发现括号都是成对的,如果成对的括号,并且括号中间的括号也可以成对,那么整个字符串就是有效的。比如说: 45 | ``` 46 | "{[]}" 47 | ``` 48 | 在{}之间的[]是成对的,它们可以互相抵消掉,之后{}也是成对的。我们可以利用栈,遍历整个字符串,遇到左括号,入栈,遇到右括号,检查栈中是否是左括号,如果是,那么就将左括号出栈,右括号也不入栈。如果字符串的是合法的,那么最终栈为空,否则栈不为空。 49 | 50 | 我们来看一个例子: 51 | ``` 52 | “{[]}” 53 | ``` 54 | 遇见左大括号,入栈: 55 | 56 | |栈顶||||| 57 | |--|--|--|--|--| 58 | |{||||| 59 | 60 | 遇见左方括号,入栈: 61 | 62 | ||栈顶|||| 63 | |--|--|--|--|--| 64 | |{|[|||| 65 | 66 | 遇见右方括号,检查栈顶是左方括号,出栈: 67 | 68 | |栈顶||||| 69 | |--|--|--|--|--| 70 | |{||||| 71 | 72 | 遇见右大括号,检查栈顶是左大括号,出栈: 73 | 74 | |||||| 75 | |--|--|--|--|--| 76 | |||||| 77 | 78 | 此时扫描完毕,并且栈为空,因此该字符串合法。 79 | 80 | 我们再来看一个非法的例子: 81 | ``` 82 | ”([)]“ 83 | ``` 84 | 85 | 首先遇到左小括号,入栈: 86 | 87 | |栈顶||||| 88 | |--|--|--|--|--| 89 | |(||||| 90 | 91 | 遇到左方括号,入栈: 92 | 93 | ||栈顶|||| 94 | |--|--|--|--|--| 95 | |(|[|||| 96 | 97 | 遇到右小括号,检查栈顶是否有左小括号,发现没有,入栈(其实这个时候就可以判断字符串不合法了): 98 | 99 | |||栈顶||| 100 | |--|--|--|--|--| 101 | |(|[|)||| 102 | 103 | 遇到右中括号,检查栈顶是否有左方括号,发现没有,入栈: 104 | 105 | ||||栈顶|| 106 | |--|--|--|--|--| 107 | |(|[|)|]|| 108 | 109 | 扫描完成后,发现栈不为空,因此字符串不合法。 110 | 111 | ## 代码实现 112 | 在实现代码的时候,需要注意以下几点: 113 | + 遇见第一个右括号无匹配时即退出 114 | + 由于输入字符串长度可能较大,因此不适合使用静态数组 115 | + 判断是否有左括号前检查栈是否为空 116 | 117 | ```c 118 | bool isValid(char* s) { 119 | if(NULL == s) 120 | return false; 121 | int len = strlen(s); 122 | /*使用数组作为栈,申请内存*/ 123 | char *stack = (char*)malloc(len * sizeof(char)); 124 | if(NULL == stack) 125 | return false; 126 | int topOfStack = -1; 127 | while(0 != *s) 128 | { 129 | /*遇见左括号入栈*/ 130 | if('{' == *s || '[' == *s || '(' == *s) 131 | { 132 | //printf("push %c to stack\n",*s); 133 | topOfStack++; 134 | stack[topOfStack] = *s; 135 | } 136 | /*如果此时栈为空,说明之前都没有左括号,因此肯定不匹配,直接退出*/ 137 | else if(topOfStack == -1) 138 | { 139 | topOfStack++; 140 | break; 141 | } 142 | /*遇见右括号,栈顶是左括号,出栈*/ 143 | else if(('}' == *s && '{' == stack[topOfStack] )|| 144 | (')' == *s && '(' == stack[topOfStack]) || 145 | ( ']' == *s && '[' == stack[topOfStack])) 146 | { 147 | topOfStack--; 148 | } 149 | /*右括号,并且栈顶不是左括号,肯定不匹配,直接退出*/ 150 | else if('}' == *s || ']' == *s || ')' == *s ) 151 | { 152 | topOfStack++; 153 | break; 154 | } 155 | s++; 156 | } 157 | free(stack); 158 | /*判断栈是否为空*/ 159 | return topOfStack == -1; 160 | 161 | } 162 | ``` 163 | 164 | 总结 165 | 本文利用栈结构在O(n)时间复杂度,O(n)空间复杂度解决了括号匹配问题。你还有什么解法?欢迎留言。 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /tree/binarySearchTree.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #define ElementType int 4 | #define SUCCESS 0 5 | #define FAILURE -1 6 | typedef struct Tree_Node 7 | { 8 | ElementType value; 9 | struct Tree_Node *left; 10 | struct Tree_Node *right; 11 | }TreeNode; 12 | 13 | /*将value插入到树中*/ 14 | TreeNode *insertTree(ElementType value,TreeNode *tree) 15 | { 16 | if(NULL == tree) 17 | { 18 | /*创建一个节点*/ 19 | tree = malloc(sizeof(TreeNode)); 20 | if(NULL == tree) 21 | { 22 | printf("malloc failed\n"); 23 | return NULL; 24 | } 25 | else 26 | { 27 | /*将节点信息存储在此叶子节点中*/ 28 | printf("insert %d to tree\n",value); 29 | tree->value = value; 30 | tree->left = NULL; 31 | tree->right = NULL; 32 | 33 | } 34 | } 35 | /*比当前节点小,则插入到左子树*/ 36 | else if(value < tree->value) 37 | { 38 | tree->left = insertTree(value,tree->left); 39 | } 40 | /*比当前节点大,则插入到右子树*/ 41 | else if(value > tree->value) 42 | { 43 | tree->right = insertTree(value,tree->right); 44 | } 45 | return tree; 46 | } 47 | 48 | /*查找值为value的节点*/ 49 | TreeNode *findTree(ElementType value,TreeNode *tree) 50 | { 51 | if(NULL == tree) 52 | { 53 | /*最后一层还没有找到*/ 54 | return NULL; 55 | } 56 | /*从左子树查找*/ 57 | if(value < tree->value) 58 | { 59 | return findTree(value,tree->left); 60 | } 61 | /*从右边子树查找*/ 62 | else if(value > tree->value) 63 | { 64 | return findTree(value,tree->right); 65 | } 66 | /*找到值*/ 67 | else 68 | return tree; 69 | 70 | } 71 | 72 | /*找到一棵树中最小的节点*/ 73 | TreeNode *findMin(TreeNode *tree) 74 | { 75 | if(NULL == tree) 76 | return NULL; 77 | else if(NULL == tree->left) 78 | return tree; 79 | else 80 | return findMin(tree->left); 81 | } 82 | TreeNode *deleteTree(ElementType value,TreeNode *tree) 83 | { 84 | TreeNode *tempNode = NULL;; 85 | if(NULL == tree) 86 | { 87 | printf("not fount \n"); 88 | return NULL; 89 | } 90 | /*比当前节点值小,从左子树查找并删除*/ 91 | else if(value < tree->value) 92 | { 93 | tree->left = deleteTree(value,tree->left); 94 | } 95 | /*比当前节点值大,从右子树查找并删除*/ 96 | else if(value > tree->value) 97 | { 98 | tree->right = deleteTree(value,tree->right); 99 | } 100 | /*等于当前节点值,并且当前节点有左右子树*/ 101 | else if(NULL != tree->left && NULL != tree->right) 102 | { 103 | /*用右子树的最小值代替该节点,并且递归删除该最小值节点*/ 104 | tempNode = findMin(tree->right); 105 | tree->value = tempNode->value; 106 | tree->right = deleteTree(tree->value,tree->right); 107 | } 108 | /*要删除的节点只有一个子节点或没有子节点*/ 109 | else 110 | { 111 | tempNode = tree; 112 | /*要删除节点有右孩子*/ 113 | if(NULL == tree->left) 114 | tree=tree->right; 115 | /*要删除节点有左孩子*/ 116 | else if(NULL == tree->right) 117 | tree = tree->left; 118 | free(tempNode); 119 | tempNode = NULL; 120 | } 121 | return tree; 122 | } 123 | 124 | /*前序遍历*/ 125 | void preOrder(TreeNode *tree) 126 | { 127 | if(NULL == tree) 128 | return; 129 | printf("%d ",tree->value); 130 | preOrder(tree->left); 131 | preOrder(tree->right); 132 | } 133 | /*中序遍历*/ 134 | void inOrder(TreeNode *tree) 135 | { 136 | if(NULL == tree) 137 | return; 138 | inOrder(tree->left); 139 | printf("%d ",tree->value); 140 | inOrder(tree->right); 141 | } 142 | 143 | /*后序遍历*/ 144 | void postOrder(TreeNode *tree) 145 | { 146 | if(NULL == tree) 147 | return; 148 | postOrder(tree->left); 149 | postOrder(tree->right); 150 | printf("%d ",tree->value); 151 | } 152 | int main(void) 153 | { 154 | int a[] = {10,5,19,4,8,13,24}; 155 | TreeNode *tree = NULL; 156 | for(int i = 0;i < 7;i++) 157 | { 158 | tree = insertTree(a[i],tree); 159 | } 160 | TreeNode *temp = NULL; 161 | temp = findTree(13,tree); 162 | if(NULL != temp) 163 | printf("find %d\n",temp->value); 164 | printf("前序遍历结果:"); 165 | preOrder(tree); 166 | printf("\n"); 167 | 168 | printf("中序遍历结果:"); 169 | inOrder(tree); 170 | printf("\n"); 171 | 172 | printf("后序遍历结果:"); 173 | postOrder(tree); 174 | printf("\n"); 175 | 176 | deleteTree(13,tree); 177 | 178 | inOrder(tree); 179 | printf("\n"); 180 | 181 | deleteTree(19,tree); 182 | 183 | inOrder(tree); 184 | printf("\n"); 185 | return 0; 186 | } 187 | -------------------------------------------------------------------------------- /priorityQueue/priorityQueue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | typedef int ElementType; 5 | typedef struct HeapStruct 6 | { 7 | int capacity; //最大元素数量 8 | int size; //堆元素数量 9 | ElementType *eles; //堆元素数组 10 | }PriorityQueue; 11 | 12 | #define true 1 13 | #define false 0 14 | #define SUCCESS 0 15 | #define FAILURE 1 16 | PriorityQueue *init_PQ(int maxEleNum) 17 | { 18 | PriorityQueue *pq = NULL; 19 | 20 | /*检查输入大小的合法性*/ 21 | if(maxEleNum <= 0 ) 22 | return NULL; 23 | pq = malloc(sizeof(PriorityQueue)); 24 | if(NULL == pq) 25 | { 26 | printf("malloc failed\n"); 27 | return NULL; 28 | } 29 | /*下标为0的位置保留,不作使用*/ 30 | pq->eles = malloc((maxEleNum + 1)*sizeof(ElementType)); 31 | if(NULL == pq->eles) 32 | { 33 | printf("malloc failed\n"); 34 | free(pq); 35 | return NULL; 36 | } 37 | 38 | /*初始化成员*/ 39 | memset(pq->eles,0,(maxEleNum + 1)*sizeof(ElementType)); 40 | pq->capacity = maxEleNum; 41 | pq->size = 0; 42 | return pq; 43 | } 44 | /*判断优先队列是否满*/ 45 | int pq_is_full(PriorityQueue *pq) 46 | { 47 | if(NULL == pq) 48 | return false; 49 | if(pq->capacity == pq->size) 50 | return true; 51 | else 52 | return false; 53 | } 54 | /*判断优先队列是否为空*/ 55 | int pq_is_empty(PriorityQueue *pq) 56 | { 57 | if(NULL == pq) 58 | return false; 59 | if(0 == pq->size) 60 | return true; 61 | else 62 | return false; 63 | } 64 | int insert_pq(ElementType value,PriorityQueue *pq) 65 | { 66 | int i =0; 67 | 68 | /*确保优先队列没有满*/ 69 | if(pq_is_full(pq)) 70 | { 71 | printf("priorityQueue is full\n"); 72 | return FAILURE; 73 | } 74 | printf("insert %d\n",value); 75 | /*不断和父节点探测比较,直到找到属于它的位置*/ 76 | for(i = pq->size+1;pq->eles[i/2] > value && i > 1;i/=2) 77 | { 78 | pq->eles[i] = pq->eles[i/2]; 79 | } 80 | pq->eles[i] = value; 81 | pq->size++; 82 | return SUCCESS; 83 | } 84 | int find_min(PriorityQueue *pq,ElementType *value) 85 | { 86 | if(pq_is_empty(pq)) 87 | { 88 | printf("priorityQueue is empty\n"); 89 | return FAILURE; 90 | } 91 | /*0处的元素作为哨兵没有使用*/ 92 | *value = pq->eles[1]; 93 | return SUCCESS; 94 | } 95 | int delete_min(PriorityQueue *pq,ElementType *min) 96 | { 97 | int i = 1; 98 | int minChild =0; 99 | if(pq_is_empty(pq)) 100 | { 101 | printf("priorityqueue is empty\n"); 102 | return FAILURE; 103 | } 104 | /*取得最小值*/ 105 | *min = pq->eles[1]; 106 | 107 | /*暂时取出最后的元素*/ 108 | ElementType last = pq->eles[pq->size]; 109 | pq->size--; 110 | if(0 == pq->size) 111 | { 112 | pq->eles[i] = 0; 113 | return SUCCESS; 114 | } 115 | /*不断将空穴下滑*/ 116 | for(i = 1;i * 2 <= pq->size;i = minChild) 117 | { 118 | minChild = i * 2; 119 | /*找到更小的孩子*/ 120 | if(minChild != pq->size && pq->eles[minChild+1] < pq->eles[minChild]) 121 | minChild+=1; 122 | 123 | /*如果最后一个元素比空穴处的小儿子大,则继续下滑空穴,将该孩子上滤*/ 124 | if(last >pq->eles[minChild]) 125 | pq->eles[i] = pq->eles[minChild]; 126 | /*否则说明last放的位置不会破坏堆性质,则直接退出循环*/ 127 | else 128 | break; 129 | } 130 | 131 | /*将最后的元素放在空穴位置*/ 132 | pq->eles[i] = last; 133 | return SUCCESS; 134 | } 135 | /*销毁队列,按照与申请内存时相反的顺序释放内存*/ 136 | int destory_pq(PriorityQueue *pq) 137 | { 138 | if(NULL == pq) 139 | { 140 | return FAILURE; 141 | } 142 | free(pq->eles); 143 | pq->eles = NULL; 144 | free(pq); 145 | pq = NULL; 146 | printf("destory pq success\n"); 147 | return SUCCESS; 148 | } 149 | int main(void) 150 | { 151 | /*创建可以容纳6个元素的优先队列*/ 152 | PriorityQueue *pq = init_PQ(6); 153 | 154 | int arr[]={3,4,5,6,8,2,9,10}; 155 | int i =0; 156 | /*试图插入多余6个的数据,最后两个会由于队列满而无法插入*/ 157 | for(i = 0;i < 8;i++) 158 | { 159 | insert_pq(arr[i],pq); 160 | } 161 | 162 | /*遍历队列数组内容*/ 163 | printf("the arr value is:"); 164 | for(i=0;i < pq->size;i++) 165 | { 166 | printf(" %d",pq->eles[i+1]); 167 | } 168 | printf("\n"); 169 | printf("pq size is %d\n",pq->size); 170 | ElementType min; 171 | int size = pq->size; 172 | /*每次都从堆顶取元素*/ 173 | for(i=0;i < size ;i++) 174 | { 175 | if(SUCCESS == find_min(pq,&min)) 176 | { 177 | printf("the min is %d\n",min); 178 | delete_min(pq,&min); 179 | } 180 | } 181 | 182 | /*销毁优先队列*/ 183 | destory_pq(pq); 184 | return 0; 185 | } 186 | -------------------------------------------------------------------------------- /binarySearch/findNotExistOne.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define MAX_STR 10 5 | #define SOURCE_FILE "source.txt" //最原始文件,需要保留 6 | #define SRC_FILE "src.txt" //需要分类的文件 7 | #define BIT_1_FILE "bit1.txt" 8 | #define BIT_0_FILE "bit0.txt" 9 | #define INT_BIT_NUM 32 10 | /* 11 | FILE *src 源数据文件指针 12 | FILE *fpBit1 存储要处理的比特位为1的数据 13 | FILE *fpBit0 存储要处理的比特位为0的数据 14 | int bit 要处理的比特位 15 | 返回值 16 | 0:选择比特位为0的数据继续处理 17 | 1:选择比特位为1的数据继续处理 18 | -1:出错 19 | */ 20 | int splitByBit(FILE *src,FILE *fpBit1,FILE *fpBit0,int bit,int *nums) 21 | { 22 | /*入参检查*/ 23 | if(NULL == src || NULL == fpBit1 || NULL == fpBit0 || NULL == nums) 24 | { 25 | printf("input para is NULL"); 26 | return -1; 27 | } 28 | /*bit位检查*/ 29 | if(bit < 0 || bit > INT_BIT_NUM ) 30 | { 31 | printf("the bit is wrong"); 32 | return -1; 33 | } 34 | char string[MAX_STR] = {0}; 35 | int mask = 1<< bit; 36 | int bit0num = 0; 37 | int bit1num = 0; 38 | int num = 0; 39 | //printf("mask is %x\n",mask); 40 | /*循环读取源数据*/ 41 | while(fgets(string, MAX_STR, src ) != NULL) 42 | { 43 | num = atoi(string); 44 | //printf("%d&%d %d\n",num,mask, num&mask); 45 | /*根据比特位的值,将数据写到不同的位置,注意优先级问题*/ 46 | if(0 == (num&mask)) 47 | { 48 | //printf("bit 0 %d\n",num); 49 | fprintf(fpBit0, "%d\n", num); 50 | bit0num++; 51 | } 52 | else 53 | { 54 | //printf("bit 1 %d\n",num); 55 | fprintf(fpBit1, "%d\n", num); 56 | bit1num++; 57 | } 58 | } 59 | //printf("bit0num:%d,bit1num:%d\n",bit0num,bit1num); 60 | if(bit0num > bit1num) 61 | { 62 | /*说明比特位为1的数少*/ 63 | *nums = bit1num; 64 | return 1; 65 | } 66 | else 67 | { 68 | *nums = bit0num; 69 | return 0; 70 | } 71 | } 72 | /*** 73 | *关闭所有文件描述符 74 | * 75 | * **/ 76 | void closeAllFile(FILE **src,FILE **bit0,FILE **bit1) 77 | { 78 | if(NULL != src && NULL != *src) 79 | { 80 | fclose(*src); 81 | *src = NULL; 82 | } 83 | if(NULL != bit1 && NULL != *bit1) 84 | { 85 | fclose(*bit1); 86 | *bit1 = NULL; 87 | } 88 | if(NULL != bit0 && NULL != *bit0) 89 | { 90 | fclose(*bit0); 91 | *bit0 = NULL; 92 | } 93 | } 94 | int findNum(int *findNum) 95 | { 96 | int loop = 0; 97 | /*打开最原始文件*/ 98 | FILE *src = fopen(SOURCE_FILE,"r"); 99 | if(NULL == src) 100 | { 101 | printf("failed to open %s",SOURCE_FILE); 102 | return -1; 103 | } 104 | FILE *bit1 = NULL; 105 | FILE *bit0 = NULL; 106 | int num = 0; 107 | int bitNums = 0; //得到比特位的数字数量 108 | int findBit = 0; //当前得到的比特位 109 | for(loop = 0; loop < INT_BIT_NUM;loop++) 110 | { 111 | /*第一次循环不会打开,保留源文件*/ 112 | if(NULL == src) 113 | { 114 | src = fopen(SRC_FILE,"r"); 115 | } 116 | if(NULL == src) 117 | { 118 | return -1; 119 | } 120 | 121 | /**打开失败时,注意关闭所有打开的文件描述符**/ 122 | bit1 = fopen(BIT_1_FILE,"w+"); 123 | if(NULL == bit1) 124 | { 125 | closeAllFile(&src,&bit1,&bit0); 126 | printf("failed to open %s",BIT_1_FILE); 127 | return -1; 128 | } 129 | bit0 = fopen(BIT_0_FILE,"w+"); 130 | if(NULL == bit0) 131 | { 132 | closeAllFile(&src,&bit1,&bit0); 133 | printf("failed to open %s",BIT_0_FILE); 134 | return -1; 135 | } 136 | findBit = splitByBit(src,bit1,bit0,loop,&bitNums); 137 | if(-1 == findBit) 138 | { 139 | printf("process error\n"); 140 | closeAllFile(&src,&bit1,&bit0); 141 | return -1; 142 | } 143 | closeAllFile(&src,&bit1,&bit0); 144 | //printf("find bit %d\n",findBit); 145 | /*将某比特位数量少的文件重命名为新的src.txt,以便进行下一次处理*/ 146 | if(1 == findBit) 147 | { 148 | rename(BIT_1_FILE,SRC_FILE); 149 | num |= (1 << loop); 150 | printf("mv bit1 file to src file\n"); 151 | } 152 | else 153 | { 154 | printf("mv bit0 file to src file\n"); 155 | rename(BIT_0_FILE,SRC_FILE); 156 | } 157 | 158 | /*如果某个文件数量为0,则没有必要继续寻找下去*/ 159 | if(0 == bitNums) 160 | { 161 | printf("no need to continue\n"); 162 | break; 163 | } 164 | } 165 | *findNum = num; 166 | return 0; 167 | } 168 | int main(void) 169 | { 170 | int num = 0; 171 | findNum(&num); 172 | printf("final num is %d or 0x%x\n",num,num); 173 | return 0; 174 | } 175 | -------------------------------------------------------------------------------- /queue/队列-C语言实现.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | title: 队列-c语言实现 4 | date: 2019-3-28 20:00:00 5 | comments: true 6 | categories: 数据结构与算法 7 | tags: [C,数据结构与算法,队列] 8 | 9 | --- 10 | 原文地址:[队列-c语言实现](https://www.yanbinghu.com/2019/03/28/13055.html) 11 | ## 前言 12 | 队列是一种先进先出的数据结构,也是常见的数据结构之一。日常生活中的排队买东西就是一种典型的队列,而在购票系统也需要一个队列处理用户的购票请求,当然这里的队列就复杂多了。本文介绍队列的基本概念和实现。 13 | 14 | ## 队列常见操作 15 | 队列最常见的操作是入队和出队,拿排队买东西来说,入队就是新来一个人排在队伍后面,而出队就是一个人已经结账离开。 16 | 17 | ## 队列基本实现考虑 18 | 与实现栈不同,它需要两个指针,一个指向队头(front),一个指向队尾(rear),这样才能方便地进行入队或出队操作,因此队列的实现要比栈难一些。在说明如何实现一个队列之前,先看实现一个队列可能需要注意哪些问题。 19 | 20 | 假如我们使用数组按照实现栈的方式来实现队列。并且队列中的数据如下: 21 | 22 | |0|1|2|3|4| 23 | |--|--|--|--|--| 24 | |10|11|12|13|14| 25 | |front||||rear| 26 | 27 | 这个时候从队头front处删除一个数据,这是很容易的。 28 | 29 | |0|1|2|3|4| 30 | |--|--|--|--|--| 31 | ||11|12|13|14| 32 | ||front|||rear| 33 | 34 | 35 | 但是如果要入队一个数据呢?这个时候发现队尾已经没有空间了,为了入队一个元素,必须将所有元素都往队头移动,这似乎很符合我们排队的习惯,前面一个人走了,后面的人都往前一个位置。**但是在数组中,将所有的元素都往队头移动的开销是不容忽略的!** 36 | 37 | 有人可能已经注意到了,原来删除的地方还有一个空位呢,不如把新的元素加入到这里,然后将尾指针rear指向该处即可。没错,这就相当于**把该数组当成了一个循环数组**,即可以看成数组尾部和头部是连着的。这个时候就变成了下面的情况: 38 | 39 | |0|1|2|3|4| 40 | |--|--|--|--|--| 41 | |15|11|12|13|14| 42 | |rear|front|||| 43 | 44 | 此时,队列是满的,rear指向下标0处,而front指向下标1处。如果这个时候删除这五个元素,就变成下面的情况: 45 | 46 | |0|1|2|3|4| 47 | |--|--|--|--|--| 48 | | | | | | | 49 | |rear|front|||| 50 | 51 | 我们发现队列为空时,rear指向下标0处,而front指向下标1处,与队列满时是一样的,这样的话,如何区分队列是空还是满呢? 52 | 53 | 很明显,队列为空时与队列满时元素数量是不一样的,我们可以通过**判断队列中元素的数量是否已达上限,来判断队列是否为空。** 54 | 55 | 还有一种方法,就是使得**队列满时,不占满整个数组,而保留一个空位。这样的情况下,队列满时,两个指针相隔2**: 56 | 57 | |0|1|2|3|4| 58 | |--|--|--|--|--| 59 | |15||12|13|14| 60 | |rear||front||| 61 | 62 | 队列空时: 63 | 64 | |0|1|2|3|4| 65 | |--|--|--|--|--| 66 | |||||| 67 | |rear|front|||| 68 | 69 | 两个指针相隔1。那么就可以通过指针的间隔来判断队列是否为空了。 70 | 71 | 这里就说明了队列实现需要考虑的两个问题: 72 | + 如何高效地将元素入队 73 | + 如何判断队列为空或队列为满 74 | 75 | 当然了,如果你使用链表实现队列,那么入队也完全不需要搬移数据。 76 | ## 实现 77 | 队列的实现有多种方式可以选择,例如: 78 | + 静态数组,容量固定,操作简便,效率高 79 | + 动态数组,容量可自定义,操作简便,因为涉及内存的申请与释放,因此实现比静态数组稍微复杂一点点 80 | + 链表,容量理论上只受限于内存,实现也较为复杂 81 | + 其他 82 | 83 | 因篇幅有限,本文只选择一种实现进行说明。 84 | 85 | 86 | 本文的代码实现采用了静态数组实现一个队列,并且为了避免数据频繁搬移,使用了前面介绍的循环队列;为了测试队满的情况,将队列的容量设置成了很小的值,另外,也通过保留一个空位的方式来解决队空和队满无法区分的问题。 87 | 88 | 队列的主要定义或操作解释如下。 89 | + 队列结构定义,定义一个结构体,包含存储队头位置和队尾位以及队列数组 90 | + 队列初始化,初始时,队尾值为0,队头值为1,两者差值为1 91 | + 队列是否为空,队尾和队头差值并取模为1时,表明队列为空 92 | + 队列是否已满,队尾和队头差值取模为2时,表明队列满 93 | + 入队,入队前检查队列是否已满,如未满,队尾加1取模,并赋值 94 | + 出队,出队前检查队列是否为空,如不空,则取值,并加1取模 95 | 96 | ## 代码 97 | 完整可运行代码实现如下: 98 | ```c 99 | //arrayQueue.c 100 | #include 101 | #include 102 | typedef int ElementType; 103 | 104 | /*为测试,将容量值定为较小值5*/ 105 | #define MAX_SIZE 5 106 | 107 | /*定义队列结构*/ 108 | typedef struct QueueInfo 109 | { 110 | int front; //队头位置 111 | int rear; //队尾位置 112 | ElementType queueArr[MAX_SIZE];//队列数组 113 | }QueueInfo; 114 | 115 | #define SUCCESS 0 116 | #define FAILURE 1 117 | #define FALSE 0 118 | #define TRUE 1 119 | 120 | /*判断队列是否已满*/ 121 | int queue_is_full(QueueInfo *queue) 122 | { 123 | if((queue->rear + 2) % MAX_SIZE == queue->front) 124 | { 125 | printf("queue is full\n"); 126 | return TRUE; 127 | } 128 | else 129 | return FALSE; 130 | } 131 | /*判断队列是否为满*/ 132 | int queue_is_empty(QueueInfo *queue) 133 | { 134 | if((queue->rear + 1) % MAX_SIZE == queue->front) 135 | { 136 | printf("queue is empty\n"); 137 | return TRUE; 138 | } 139 | else 140 | return FALSE; 141 | } 142 | 143 | /*出队*/ 144 | int queue_delete(QueueInfo *queue,ElementType *value) 145 | { 146 | if(queue_is_empty(queue)) 147 | return FAILURE; 148 | 149 | *value = queue->queueArr[queue->front]; 150 | printf("get value from front %d is %d\n",queue->front,*value); 151 | queue->front = (queue->front + 1) % MAX_SIZE; 152 | return SUCCESS; 153 | } 154 | 155 | /*入队*/ 156 | int queue_insert(QueueInfo *queue,ElementType value) 157 | { 158 | if(queue_is_full(queue)) 159 | return FAILURE; 160 | 161 | queue->rear = (queue->rear + 1) % MAX_SIZE; 162 | queue->queueArr[queue->rear] = value; 163 | printf("insert %d to %d\n",value,queue->rear); 164 | return SUCCESS; 165 | } 166 | int main(void) 167 | { 168 | /*队列初始化*/ 169 | QueueInfo queue; 170 | memset(&queue,0,sizeof(queue)); 171 | queue.front = 1; 172 | queue.rear = 0; 173 | 174 | /*入队6个数据,最后两个入队失败*/ 175 | queue_insert(&queue,5); 176 | queue_insert(&queue,4); 177 | queue_insert(&queue,3); 178 | queue_insert(&queue,2); 179 | queue_insert(&queue,1); 180 | queue_insert(&queue,0); 181 | 182 | /*出队6个数据,最后两个出队失败*/ 183 | ElementType a = 0; 184 | queue_delete(&queue,&a); 185 | queue_delete(&queue,&a); 186 | queue_delete(&queue,&a); 187 | queue_delete(&queue,&a); 188 | queue_delete(&queue,&a); 189 | queue_delete(&queue,&a); 190 | return 0; 191 | } 192 | 193 | ``` 194 | 编译: 195 | ``` 196 | $ gcc -o arrayQueue arrayQueue.c 197 | ``` 198 | 运行结果: 199 | ``` 200 | insert 5 to 1 201 | insert 4 to 2 202 | insert 3 to 3 203 | insert 2 to 4 204 | queue is full 205 | queue is full 206 | get value from front 1 is 5 207 | get value from front 2 is 4 208 | get value from front 3 is 3 209 | get value from front 4 is 2 210 | queue is empty 211 | queue is empty 212 | ``` 213 | 214 | ## 总结 215 | 与《[栈的实现](https://www.yanbinghu.com/2019/03/16/31765.html)》相比,队列的数组实现相对来说稍微复杂一点,因为它需要考虑队空和对满的区别,以及考虑数据搬移的性能影响,但是从实现本身来看,代码并不复杂。关于队列的动态数组和链表实现可自己尝试。 216 | -------------------------------------------------------------------------------- /tree/traversal.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /*定义树节点*/ 6 | typedef struct Tree_Node 7 | { 8 | int value; //节点值 9 | struct Tree_Node *left; //左节点 10 | struct Tree_Node *right; //右节点 11 | }TreeNode; 12 | typedef TreeNode *ElementType; 13 | /*为测试,将容量值定为较小值5*/ 14 | #define MAX_SIZE 64 15 | /*定义队列结构*/ 16 | typedef struct QueueInfo 17 | { 18 | int front; //队头位置 19 | int rear; //队尾位置 20 | ElementType queueArr[MAX_SIZE];//队列数组 21 | }QueueInfo; 22 | 23 | #define SUCCESS 0 24 | #define FAILURE 1 25 | #define FALSE 0 26 | #define TRUE 1 27 | /*判断队列是否已满*/ 28 | int queue_is_full(QueueInfo *queue) 29 | { 30 | if((queue->rear + 2) % MAX_SIZE == queue->front) 31 | { 32 | printf("queue is full\n"); 33 | return TRUE; 34 | } 35 | else 36 | return FALSE; 37 | } 38 | /*判断队列是否为空*/ 39 | int queue_is_empty(QueueInfo *queue) 40 | { 41 | if((queue->rear + 1) % MAX_SIZE == queue->front) 42 | { 43 | //printf("queue is empty\n"); 44 | return TRUE; 45 | } 46 | else 47 | return FALSE; 48 | } 49 | /*出队*/ 50 | int queue_delete(QueueInfo *queue,ElementType *value) 51 | { 52 | if(queue_is_empty(queue)) 53 | return FAILURE; 54 | 55 | *value = queue->queueArr[queue->front]; 56 | // printf("get value from front %d is %p\n",queue->front,*value); 57 | queue->front = (queue->front + 1) % MAX_SIZE; 58 | return SUCCESS; 59 | } 60 | /*入队*/ 61 | int queue_insert(QueueInfo *queue,ElementType value) 62 | { 63 | if(queue_is_full(queue)) 64 | return FAILURE; 65 | 66 | queue->rear = (queue->rear + 1) % MAX_SIZE; 67 | queue->queueArr[queue->rear] = value; 68 | // printf("insert %d to %d\n",value,queue->rear); 69 | return SUCCESS; 70 | } 71 | void init_queue(QueueInfo *queue) 72 | { 73 | memset(queue,0,sizeof(QueueInfo)); 74 | queue->front = 1; 75 | queue->rear = 0; 76 | } 77 | /*将value插入到树中*/ 78 | TreeNode *insertTree(int value,TreeNode *tree) 79 | { 80 | if(NULL == tree) 81 | { 82 | /*创建一个节点*/ 83 | tree = malloc(sizeof(TreeNode)); 84 | if(NULL == tree) 85 | { 86 | printf("malloc failed\n"); 87 | return NULL; 88 | } 89 | else 90 | { 91 | /*将节点信息存储在此叶子节点中*/ 92 | printf("insert %d to tree\n",value); 93 | tree->value = value; 94 | tree->left = NULL; 95 | tree->right = NULL; 96 | 97 | } 98 | } 99 | /*比当前节点小,则插入到左子树*/ 100 | else if(value < tree->value) 101 | { 102 | tree->left = insertTree(value,tree->left); 103 | } 104 | /*比当前节点大,则插入到右子树*/ 105 | else if(value > tree->value) 106 | { 107 | tree->right = insertTree(value,tree->right); 108 | } 109 | return tree; 110 | } 111 | /*前序遍历*/ 112 | void preOrder(TreeNode *tree) 113 | { 114 | if(NULL == tree) 115 | return; 116 | printf("%d ",tree->value); 117 | preOrder(tree->left); 118 | preOrder(tree->right); 119 | } 120 | /*中序遍历*/ 121 | void inOrder(TreeNode *tree) 122 | { 123 | if(NULL == tree) 124 | return; 125 | inOrder(tree->left); 126 | printf("%d ",tree->value); 127 | inOrder(tree->right); 128 | } 129 | 130 | /*后序遍历*/ 131 | void postOrder(TreeNode *tree) 132 | { 133 | if(NULL == tree) 134 | return; 135 | postOrder(tree->left); 136 | postOrder(tree->right); 137 | printf("%d ",tree->value); 138 | } 139 | 140 | /*层次遍历*/ 141 | void PrintNodeByLevel(TreeNode* root) 142 | { 143 | if(NULL == root) 144 | return; 145 | /*初始化队列*/ 146 | QueueInfo queue; 147 | init_queue(&queue); 148 | TreeNode *node = NULL; 149 | 150 | /*头节点入队*/ 151 | queue_insert(&queue,root); 152 | do 153 | { 154 | queue_delete(&queue,&node); 155 | if (node) 156 | { 157 | printf("%d ",node->value); 158 | if (node->left) 159 | queue_insert(&queue,node->left); 160 | if (node->right) 161 | queue_insert(&queue,node->right); 162 | } 163 | } while (!queue_is_empty(&queue)); 164 | } 165 | 166 | /** 167 | * leetcode 94题 168 | * Definition for a binary tree node. 169 | * struct TreeNode { 170 | * int val; 171 | * struct TreeNode *left; 172 | * struct TreeNode *right; 173 | * }; 174 | */ 175 | /** 176 | * Return an array of size *returnSize. 177 | * Note: The returned array must be malloced, assume caller calls free(). 178 | */ 179 | int* inorderTraversal(TreeNode* root, int* returnSize) { 180 | if(NULL == root || NULL == returnSize) 181 | { 182 | *returnSize = 0; 183 | return NULL; 184 | } 185 | int numleft = 0; 186 | int numRight = 0; 187 | 188 | /*递归计算左右子树,并将左右子树的返回结果存储在最终结果里*/ 189 | int *left=inorderTraversal(root->left,&numleft); 190 | int *right=inorderTraversal(root->right,&numRight); 191 | int *array = malloc(sizeof(int)*(numleft+numRight+1)); 192 | if(NULL == array) 193 | return NULL; 194 | if(left) 195 | memcpy(array,left,sizeof(int)*numleft); 196 | 197 | array[numleft] = root->value; 198 | 199 | if(right) 200 | memcpy(array+numleft+1,right,sizeof(int)*numRight); 201 | 202 | *returnSize = numleft+numRight+1; 203 | free(left); 204 | left = NULL; 205 | free(right); 206 | right = NULL; 207 | return array; 208 | 209 | } 210 | 211 | int main(void) 212 | { 213 | int a[] = {10,5,19,4,8,13,24}; 214 | TreeNode *tree = NULL; 215 | for(int i = 0;i < 7;i++) 216 | { 217 | tree = insertTree(a[i],tree); 218 | } 219 | printf("\n层次遍历:"); 220 | PrintNodeByLevel(tree); 221 | 222 | printf("\n前序遍历:"); 223 | preOrder(tree); 224 | 225 | printf("\n后序遍历:"); 226 | postOrder(tree); 227 | 228 | printf("\n中序遍历:"); 229 | inOrder(tree); 230 | printf("\n中序遍历:"); 231 | int size = 0; 232 | int *ret = inorderTraversal(tree,&size); 233 | 234 | int i = 0; 235 | while(i < size) 236 | { 237 | printf("%d ",ret[i]); 238 | i++; 239 | } 240 | free(ret); 241 | printf("\n"); 242 | return 0; 243 | } 244 | -------------------------------------------------------------------------------- /stack/expressionEvaluation.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #define STACK_SIZE 64 /*栈大小*/ 4 | #define TOP_OF_STACK -1 /*栈顶位置*/ 5 | typedef int ElementType; /*栈元素类型*/ 6 | 7 | #define SUCCESS 0 8 | #define FAILURE -1 9 | 10 | /*定义栈结构*/ 11 | typedef struct StackInfo 12 | { 13 | int topOfStack; /*记录栈顶位置*/ 14 | ElementType stack[STACK_SIZE]; /*栈数组,也可以使用动态数组实现*/ 15 | }StackInfo_st; 16 | 17 | 18 | /*函数声明*/ 19 | int stack_push(StackInfo_st *s,ElementType value); 20 | int stack_pop(StackInfo_st *s,ElementType *value); 21 | int stack_top(StackInfo_st *s,ElementType *value); 22 | int stack_is_full(StackInfo_st *s); 23 | int stack_is_empty(StackInfo_st *s); 24 | 25 | 26 | /*入栈,0表示成,非0表示出错*/ 27 | int stack_push(StackInfo_st *s,ElementType value) 28 | { 29 | if(stack_is_full(s)) 30 | return FAILURE; 31 | /*先增加topOfStack,再赋值*/ 32 | s->topOfStack++; 33 | s->stack[s->topOfStack] = value; 34 | return SUCCESS; 35 | } 36 | 37 | /*出栈*/ 38 | int stack_pop(StackInfo_st *s,ElementType *value) 39 | { 40 | /*首先判断栈是否为空*/ 41 | if(stack_is_empty(s)) 42 | return FAILURE; 43 | *value = s->stack[s->topOfStack]; 44 | s->topOfStack--; 45 | return SUCCESS; 46 | } 47 | /*访问栈顶元素*/ 48 | int stack_top(StackInfo_st *s,ElementType *value) 49 | { 50 | /*首先判断栈是否为空*/ 51 | if(stack_is_empty(s)) 52 | { 53 | printf("stack is empty"); 54 | return FAILURE; 55 | } 56 | *value = s->stack[s->topOfStack]; 57 | return SUCCESS; 58 | } 59 | 60 | /*判断栈是否已满,满返回1,未满返回0*/ 61 | int stack_is_full(StackInfo_st *s) 62 | { 63 | return s->topOfStack == STACK_SIZE - 1; 64 | } 65 | /*判断栈是否为空,空返回1,非空返回0*/ 66 | int stack_is_empty(StackInfo_st *s) 67 | { 68 | return s->topOfStack == - 1; 69 | } 70 | 71 | /*用于记录符号的优先级,这里浪费了一些内存,可以优化*/ 72 | static char priority[128] = {0}; 73 | void priorityInit() 74 | { 75 | /*初始化优先级,值越小,优先级越高*/ 76 | priority['+'] = 4; 77 | priority['-'] = 4; 78 | priority['*'] = 3; 79 | priority['/'] = 3; 80 | priority['('] = 1; 81 | priority[')'] = 1; 82 | 83 | 84 | } 85 | /*比较运算符的优先级,op1优先级大于op2时,返回大于0的值*/ 86 | int priorityCompare(char op1,char op2) 87 | { 88 | return priority[op2] - priority[op1]; 89 | } 90 | /*出栈操作符和操作数进行计算*/ 91 | int calcOp(StackInfo_st *nums,StackInfo_st *ops,int nowOp) 92 | { 93 | int a ,b,op; 94 | stack_pop(ops,&op); 95 | printf("op %c is <= %c\n",nowOp,op); 96 | printf("get op from stack %c\n",op); 97 | if(SUCCESS != stack_pop(nums,&b)) 98 | { 99 | printf("pop failed\n"); 100 | return -1; 101 | } 102 | if(SUCCESS != stack_pop(nums,&a)) 103 | { 104 | printf("pop failed\n"); 105 | return 0; 106 | } 107 | printf("get b from stack %d\n",b); 108 | 109 | printf("get a from stack %d\n",a); 110 | switch(op) 111 | { 112 | case '+': 113 | { 114 | printf("push %d into stack\n",a+b); 115 | stack_push(nums,a+b); 116 | break; 117 | } 118 | case '-': 119 | { 120 | stack_push(nums,a-b); 121 | break; 122 | } 123 | case '*': 124 | { 125 | printf("push %d into stack\n",a*b); 126 | stack_push(nums,a*b); 127 | break; 128 | } 129 | case '/': 130 | { 131 | printf("push %d into stack\n",a/b); 132 | stack_push(nums,a/b); 133 | break; 134 | } 135 | } 136 | return 1; 137 | } 138 | int calc(const char* exp,int *result) 139 | { 140 | if(NULL == exp || NULL == result) 141 | return FAILURE; 142 | /*创建栈,用于保存数*/ 143 | StackInfo_st nums; 144 | nums.topOfStack = TOP_OF_STACK; 145 | 146 | /*用于保存操作符*/ 147 | StackInfo_st ops; 148 | ops.topOfStack = TOP_OF_STACK; 149 | int index = 0; 150 | /*用于标记,判断上一个是否为数字*/ 151 | int flag = 0; 152 | int temp = 0; 153 | int op ; 154 | while(0 != *exp) 155 | { 156 | /*如果是数字*/ 157 | if(isdigit(*exp)) 158 | { 159 | printf("char is %c\n",*exp); 160 | /*如果上一个还是数字,则取出栈顶数据*/ 161 | if(1 == flag) 162 | { 163 | 164 | stack_pop(&nums,&temp); 165 | printf("pop from stack num %d\n",temp); 166 | } 167 | else 168 | temp = 0; 169 | flag = 1; 170 | temp = 10 * temp + *exp-'0'; 171 | printf("push %d to stack\n",temp); 172 | stack_push(&nums,temp); 173 | } 174 | /*如果是操作符*/ 175 | else if('/' == *exp || '*' == *exp || '+' == *exp || '-' == *exp) 176 | { 177 | flag = 0; 178 | printf("OP is %c\n",*exp); 179 | while((ops.topOfStack > TOP_OF_STACK )&&(SUCCESS == stack_top(&ops,&op))&&'(' != op && ')'!=op&&(priorityCompare(*exp,op) < 0)) 180 | { 181 | calcOp(&nums, &ops,*exp); 182 | } 183 | printf("push %c to stack ops\n",*exp); 184 | stack_push(&ops,*exp); 185 | } 186 | /*左括号直接入栈*/ 187 | else if('(' == *exp ) 188 | { 189 | printf("push ( to stack ops\n"); 190 | flag = 0; 191 | stack_push(&ops,*exp); 192 | } 193 | /*右括号,计算*/ 194 | else if(')' ==*exp ) 195 | { 196 | printf("deal with ) in ops\n"); 197 | flag = 0; 198 | /*右括号时,不断计算,直到遇见左括号*/ 199 | while(SUCCESS == stack_top(&ops,&op) && '(' != op) 200 | { 201 | calcOp(&nums, &ops,*exp); 202 | } 203 | stack_pop(&ops,&op); 204 | } 205 | else 206 | { 207 | flag=0; 208 | } 209 | printf("flag is %d\n\n",flag); 210 | exp++; 211 | } 212 | /*计算剩余两个栈的内容*/ 213 | while((!stack_is_empty(&ops)) && (!stack_is_empty(&nums))) 214 | { 215 | if(!calcOp(&nums, &ops,0)) 216 | printf("exp is error\n"); 217 | } 218 | stack_pop(&nums,&temp); 219 | /*如果栈中还有内容,说明表达式错误*/ 220 | if((!stack_is_empty(&ops)) || (!stack_is_empty(&nums))) 221 | printf("\n\nexp is ok\n\n"); 222 | 223 | if(SUCCESS == stack_pop(&nums,&temp)) 224 | printf("result is %d\n",temp); 225 | *result = temp; 226 | return 0; 227 | } 228 | int main(int argc ,char *argv[]) 229 | { 230 | 231 | int result; 232 | priorityInit(); 233 | char exp[] = "6 * (2 + 3 * 3)* 8 + 5"; 234 | calc(exp,&result); 235 | printf("result is %d\n",result); 236 | return 0; 237 | } 238 | -------------------------------------------------------------------------------- /stack/expression Evaluation.md: -------------------------------------------------------------------------------- 1 | ## 如何用栈实现表达式求值 2 | 3 | ## 前言 4 | 假如要你实现一个可以识别表达式的简易计算器,你会怎么实现?例如用户输入: 5 | ``` 6 | 3 + 5 * (2 - 4) 7 | ``` 8 | 可以直接得出计算结果:-7。对于人类来说,我们很容易计算出来,因为我们从左往右看,看到后面括号时,知道括号内的计算优先级最高,因此可以先计算,然后反过来计算乘法,最后计算加法,得到最终结果。 9 | 10 | ## 后缀表达式 11 | 而对于计算机来说,也可以采用类似的顺序,先记录存储3为a,然后存储5为b,计算2-4结果存入c,再然后计算b*c存储d,最终计算a+d得到最终结果。而这种计算过程的操作顺序可描述如下(把操作符号放在操作数后面): 12 | ``` 13 | 3 5 2 4 - * + 14 | ``` 15 | 这种记法叫做后缀或逆波兰记法(而我们平常简单的叫中缀记法),它的特点是不需要用括号就能表示出整个表达式哪部分运算先进行,也就是说不需要考虑优先级,这非常符合计算机的处理方式。这种记法很容易使用我们前面介绍的栈来求值,但是前提是需要将中缀表达式先转换为后缀表达式。对于这种转换,我们也可以使用前面介绍的栈或者将要介绍的树来完成,因篇幅有限,本文不准备介绍。 16 | 17 | 接下来将会介绍如何利用中缀表达式进行求值。 18 | ## 利用栈实现中缀表达式求值 19 | 前面也说到,所谓中缀表达式,就是我们能看到的正常表达式,中缀表达式求值,也就是直接对输入的表达式进行求值。为简单起见,我们这里假设只涉及加减乘除和小括号,并且操作数都是正整数,不涉及更加复杂的运算。 20 | 21 | 计算思路: 22 | + 使用两个栈,stack0用于存储操作数,stack1用于存储操作符号 23 | + 从左往右扫描,遇到操作数入栈stack0 24 | + 遇到操作符时,如果优先级低于或等于栈顶操作符优先级,则从stack1弹出两个元素进行计算,并压入stack0,继续比较与栈顶操作符的优先级 25 | + 遇到操作符如果高于栈顶操作符优先级,则直接入栈stack1 26 | + 遇到左括号,直接入栈stack1,遇到右括号,则直接出栈并计算,直到遇到做左括号 27 | 28 | 上面的思路可能看起来不是很明确,我们举一个简单的例子,假如要对下面的表达式求值: 29 | ``` 30 | 6 * (2 + 3 )* 8 + 5 31 | ``` 32 | 我们从左往右开始扫描。首先遇到操作数6,和操作符*,分别入栈 33 | stack0: 34 | 35 | |栈顶||||| 36 | |--|--|--|--|--| 37 | |6||||| 38 | 39 | stack1: 40 | 41 | |栈顶||||| 42 | |--|--|--|--|--| 43 | |*||||| 44 | 45 | 继续往后扫描,遇到(入栈,2入栈,+入栈,3入栈 46 | stack0: 47 | 48 | |||栈顶||| 49 | |--|--|--|--|--| 50 | |6|2|3||| 51 | 52 | stack1: 53 | 54 | |||栈顶||| 55 | |--|--|--|--|--| 56 | |*|(|+||| 57 | 58 | 继续往后扫描,遇到右括号,它与栈顶操作符+相比,优先级要高,因此,将+出栈,弹出两个操作数3,2,计算结果得到5,并入栈: 59 | 60 | stack0: 61 | 62 | ||栈顶|||| 63 | |--|--|--|--|--| 64 | |6|5|||| 65 | 66 | stack1: 67 | 68 | ||栈顶|||| 69 | |--|--|--|--|--| 70 | |*|(|||| 71 | 72 | 继续出栈,直到遇到左括号 73 | stack0: 74 | 75 | ||栈顶|||| 76 | |--|--|--|--|--| 77 | |6|5|||| 78 | 79 | stack1: 80 | 81 | |栈顶||||| 82 | |--|--|--|--|--| 83 | |*||||| 84 | 85 | 继续往后扫描,遇到操作符*,优先级与栈顶*优先级相同,因此弹出操作数并计算得到30入栈,最后*入栈 86 | 87 | stack0: 88 | 89 | |栈顶||||| 90 | |--|--|--|--|--| 91 | |30||||| 92 | 93 | stack1: 94 | 95 | |栈顶||||| 96 | |--|--|--|--|--| 97 | |*||||| 98 | 99 | 继续扫描,8入栈,操作符+优先级小于*,弹出操作数计算得到结果240,并将其入栈,最后+也入栈 100 | 101 | stack0: 102 | 103 | |栈顶||||| 104 | |--|--|--|--|--| 105 | |240||||| 106 | 107 | stack1: 108 | 109 | |栈顶||||| 110 | |--|--|--|--|--| 111 | |+||||| 112 | 113 | 最后5入栈,发现操作符栈不为空,弹出操作符+和两个操作数,并进行计算,得到245,入栈,得到最终结果。 114 | 115 | |栈顶||||| 116 | |--|--|--|--|--| 117 | |245||||| 118 | 119 | stack1: 120 | 121 | |||||| 122 | |--|--|--|--|--| 123 | |||||| 124 | 125 | ## 代码实现 126 | ``` 127 | #include 128 | #include 129 | #define STACK_SIZE 64 /*栈大小*/ 130 | #define TOP_OF_STACK -1 /*栈顶位置*/ 131 | typedef int ElementType; /*栈元素类型*/ 132 | 133 | #define SUCCESS 0 134 | #define FAILURE -1 135 | 136 | /*定义栈结构*/ 137 | typedef struct StackInfo 138 | { 139 | int topOfStack; /*记录栈顶位置*/ 140 | ElementType stack[STACK_SIZE]; /*栈数组,也可以使用动态数组实现*/ 141 | }StackInfo_st; 142 | 143 | 144 | /*函数声明*/ 145 | int stack_push(StackInfo_st *s,ElementType value); 146 | int stack_pop(StackInfo_st *s,ElementType *value); 147 | int stack_top(StackInfo_st *s,ElementType *value); 148 | int stack_is_full(StackInfo_st *s); 149 | int stack_is_empty(StackInfo_st *s); 150 | 151 | 152 | /*入栈,0表示成,非0表示出错*/ 153 | int stack_push(StackInfo_st *s,ElementType value) 154 | { 155 | if(stack_is_full(s)) 156 | return FAILURE; 157 | /*先增加topOfStack,再赋值*/ 158 | s->topOfStack++; 159 | s->stack[s->topOfStack] = value; 160 | return SUCCESS; 161 | } 162 | 163 | /*出栈*/ 164 | int stack_pop(StackInfo_st *s,ElementType *value) 165 | { 166 | /*首先判断栈是否为空*/ 167 | if(stack_is_empty(s)) 168 | return FAILURE; 169 | *value = s->stack[s->topOfStack]; 170 | s->topOfStack--; 171 | return SUCCESS; 172 | } 173 | /*访问栈顶元素*/ 174 | int stack_top(StackInfo_st *s,ElementType *value) 175 | { 176 | /*首先判断栈是否为空*/ 177 | if(stack_is_empty(s)) 178 | { 179 | printf("stack is empty"); 180 | return FAILURE; 181 | } 182 | *value = s->stack[s->topOfStack]; 183 | return SUCCESS; 184 | } 185 | 186 | /*判断栈是否已满,满返回1,未满返回0*/ 187 | int stack_is_full(StackInfo_st *s) 188 | { 189 | return s->topOfStack == STACK_SIZE - 1; 190 | } 191 | /*判断栈是否为空,空返回1,非空返回0*/ 192 | int stack_is_empty(StackInfo_st *s) 193 | { 194 | return s->topOfStack == - 1; 195 | } 196 | 197 | /*用于记录符号的优先级,这里浪费了一些内存,可以优化*/ 198 | static char priority[128] = {0}; 199 | void priorityInit() 200 | { 201 | /*初始化优先级,值越小,优先级越高*/ 202 | priority['+'] = 4; 203 | priority['-'] = 4; 204 | priority['*'] = 3; 205 | priority['/'] = 3; 206 | priority['('] = 1; 207 | priority[')'] = 1; 208 | 209 | 210 | } 211 | /*比较运算符的优先级,op1优先级大于op2时,返回大于0的值*/ 212 | int priorityCompare(char op1,char op2) 213 | { 214 | printf("priority[op2] %c - priority[op1] %c %d\n",op2,op1,priority[op2] - priority[op1]); 215 | return priority[op2] - priority[op1]; 216 | } 217 | /*出栈操作符和运算符进行计算*/ 218 | int calcOp(StackInfo_st *nums,StackInfo_st *ops,int nowOp) 219 | { 220 | int a ,b,op; 221 | stack_pop(ops,&op); 222 | printf("op %c is <= %c\n",nowOp,op); 223 | printf("get op from stack %c\n",op); 224 | if(SUCCESS != stack_pop(nums,&b)) 225 | { 226 | printf("pop failed\n"); 227 | return -1; 228 | } 229 | if(SUCCESS != stack_pop(nums,&a)) 230 | { 231 | printf("pop failed\n"); 232 | return 0; 233 | } 234 | printf("get b from stack %d\n",b); 235 | 236 | printf("get a from stack %d\n",a); 237 | switch(op) 238 | { 239 | case '+': 240 | { 241 | printf("push %d into stack\n",a+b); 242 | stack_push(nums,a+b); 243 | break; 244 | } 245 | case '-': 246 | { 247 | stack_push(nums,a-b); 248 | break; 249 | } 250 | case '*': 251 | { 252 | printf("push %d into stack\n",a*b); 253 | stack_push(nums,a*b); 254 | break; 255 | } 256 | case '/': 257 | { 258 | printf("push %d into stack\n",a/b); 259 | stack_push(nums,a/b); 260 | break; 261 | } 262 | } 263 | return 1; 264 | 265 | } 266 | int calc(const char* exp,int *result) 267 | { 268 | if(NULL == exp || NULL == result) 269 | return FAILURE; 270 | /*创建栈,用于保存数*/ 271 | StackInfo_st nums; 272 | nums.topOfStack = TOP_OF_STACK; 273 | 274 | /*用于保存操作符*/ 275 | StackInfo_st ops; 276 | ops.topOfStack = TOP_OF_STACK; 277 | int index = 0; 278 | /*用于标记,判断上一个是否为数字*/ 279 | int flag = 0; 280 | int temp = 0; 281 | int op ; 282 | while(0 != *exp) 283 | { 284 | 285 | 286 | /*如果是数字*/ 287 | if(isdigit(*exp)) 288 | { 289 | printf("char is %c\n",*exp); 290 | /*如果上一个还是数字,则取出栈顶数据*/ 291 | if(1 == flag) 292 | { 293 | 294 | stack_pop(&nums,&temp); 295 | printf("pop from stack num %d\n",temp); 296 | } 297 | else 298 | temp = 0; 299 | flag = 1; 300 | temp = 10 * temp + *exp-'0'; 301 | printf("push %d to stack\n",temp); 302 | stack_push(&nums,temp); 303 | //i++; 304 | } 305 | else if('/' == *exp || '*' == *exp || '+' == *exp || '-' == *exp) 306 | { 307 | flag = 0; 308 | printf("OP is %c\n",*exp); 309 | while((ops.topOfStack > TOP_OF_STACK )&&(SUCCESS == stack_top(&ops,&op))&&'(' != op && ')'!=op&&(priorityCompare(*exp,op) < 0)) 310 | { 311 | calcOp(&nums, &ops,*exp); 312 | } 313 | printf("push %c to stack ops\n",*exp); 314 | stack_push(&ops,*exp); 315 | } 316 | /*左括号直接入栈*/ 317 | else if('(' == *exp ) 318 | { 319 | printf("push ( to stack ops\n",*exp); 320 | flag = 0; 321 | stack_push(&ops,*exp); 322 | } 323 | /*右括号,计算*/ 324 | else if(')' ==*exp ) 325 | { 326 | printf("deal with ) in ops\n",*exp); 327 | flag = 0; 328 | /*右括号时,不断计算,直到遇见左括号*/ 329 | while(SUCCESS == stack_top(&ops,&op) && '(' != op) 330 | { 331 | calcOp(&nums, &ops,*exp); 332 | } 333 | stack_pop(&ops,&op); 334 | } 335 | else 336 | { 337 | flag=0; 338 | } 339 | printf("flag is %d\n\n",flag); 340 | exp++; 341 | } 342 | /*计算剩余两个栈的内容*/ 343 | while((!stack_is_empty(&ops)) && (!stack_is_empty(&nums))) 344 | { 345 | if(!calcOp(&nums, &ops,0)) 346 | printf("exp is error\n"); 347 | } 348 | stack_pop(&nums,&temp); 349 | /*如果栈中还有内容,说明表达式错误*/ 350 | if((!stack_is_empty(&ops)) || (!stack_is_empty(&nums))) 351 | printf("\n\nexp is ok\n\n"); 352 | //printf("result is %d\n",temp); 353 | 354 | *result = temp; 355 | if(SUCCESS == stack_pop(&nums,&temp)) 356 | printf("result is %d\n",temp); 357 | if(SUCCESS == stack_pop(&ops,&temp)) 358 | printf("result is %d\n",temp); 359 | return 0; 360 | } 361 | int main(int argc ,char *argv[]) 362 | { 363 | 364 | int result; 365 | priorityInit(); 366 | char exp[] = "6 * (2 + 3 )* 8 + 5"; 367 | calc(exp,&result); 368 | printf("result is %d\n",result); 369 | return 0; 370 | } 371 | ``` 372 | 373 | ## 总结 374 | 本文介绍了利用栈对中缀表达式进行求值,而代码实现还有很多不足之处,例如对表达式的正确性校验不足,只能处理正整数等等,欢迎在此基础上完善补充。尽管如此,整个过程对使用栈进行中缀表达式的求值做了一个较为完整的介绍,在此基础上可以实现更加复杂的功能。 375 | 376 | 377 | -------------------------------------------------------------------------------- /stack/expressionEvaluation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 如何用栈实现表达式求值 3 | date: 2019-3-24 16:00:00 4 | comments: true 5 | categories: 数据结构与算法 6 | tags: [C,数据结构与算法,栈] 7 | 8 | --- 9 | ## 前言 10 | 假如要你实现一个可以识别表达式的简易计算器,你会怎么实现?例如用户输入: 11 | ``` 12 | 3 + 5 * (2 - 4) 13 | ``` 14 | 可以直接得出计算结果:-7。对于人类来说,我们很容易计算出来,因为我们从左往右看,看到后面括号时,知道括号内的计算优先级最高,因此可以先计算括号内的,然后反过来计算乘法,最后计算加法,得到最终结果。 15 | 16 | ## 后缀表达式 17 | 而对于计算机来说,实际也可以采用类似的顺序,先记录存储3为a,然后存储5为b,计算2-4结果存入c,再然后计算b*c存储d,最终计算a+d得到最终结果。而这种计算过程的操作顺序可描述如下(把操作符号放在操作数后面): 18 | ``` 19 | 3 5 2 4 - * + 20 | ``` 21 | 这种记法叫做后缀或逆波兰记法(而我们平常见到的叫中缀记法),它的特点是**不需要用括号就能表示出整个表达式哪部分运算先进行,也就是说不需要考虑优先级,这非常符合计算机的处理方式。**这种记法很容易使用我们前面介绍的栈来求值,但是前提是需要将中缀表达式先转换为后缀表达式。对于这种转换,我们也可以使用前面介绍的[栈-C语言实现](https://www.yanbinghu.com/2019/03/16/31765.html)或者将要介绍的树来完成,因篇幅有限,本文不准备介绍。 22 | 23 | 接下来将会介绍如何利用中缀表达式进行求值。 24 | ## 利用栈实现中缀表达式求值 25 | 前面也说到,所谓中缀表达式,就是我们能看到的正常表达式,中缀表达式求值,也就是直接对输入的表达式进行求值。为简单起见,我们这里假设**只涉及加减乘除和小括号,并且操作数都是正整数,不涉及更加复杂的数据或运算。** 26 | 27 | 计算思路: 28 | + 使用两个栈,stack0用于存储操作数,stack1用于存储操作符 29 | + 从左往右扫描,遇到操作数入栈stack0 30 | + 遇到操作符时,如果优先级低于或等于栈顶操作符优先级,则从stack1弹出两个元素进行计算,并压入stack0,继续与栈顶操作符的比较优先级 31 | + 如果遇到操作符高于栈顶操作符优先级,则直接入栈stack1 32 | + 遇到左括号,直接入栈stack1,遇到右括号,则直接出栈并计算,直到遇到左括号 33 | 34 | 上面的思路可能看起来不是很明确,我们举一个简单的例子,假如要对下面的表达式求值: 35 | ``` 36 | 6 * (2 + 3 )* 8 + 5 37 | ``` 38 | 我们从左往右开始扫描。首先遇到操作数‘6’,和操作符‘*’,分别入栈 39 | stack0: 40 | 41 | |栈顶||||| 42 | |--|--|--|--|--| 43 | |6||||| 44 | 45 | stack1: 46 | 47 | |栈顶||||| 48 | |--|--|--|--|--| 49 | |*||||| 50 | 51 | 继续往后扫描,遇到‘(’直接入栈,‘2’入栈,栈顶是左括号,’+‘入栈,‘3’入栈 52 | stack0: 53 | 54 | |||栈顶||| 55 | |--|--|--|--|--| 56 | |6|2|3||| 57 | 58 | stack1: 59 | 60 | |||栈顶||| 61 | |--|--|--|--|--| 62 | |*|(|+||| 63 | 64 | 继续往后扫描,遇到右括号,它与栈顶操作符‘+’相比,优先级要高,因此,将‘+’出栈,弹出两个操作数‘3’,‘2’,计算结果得到‘5’,并入栈: 65 | 66 | stack0: 67 | 68 | ||栈顶|||| 69 | |--|--|--|--|--| 70 | |6|5|||| 71 | 72 | stack1: 73 | 74 | ||栈顶|||| 75 | |--|--|--|--|--| 76 | |*|(|||| 77 | 78 | 继续出栈,直到遇到左括号 79 | stack0: 80 | 81 | ||栈顶|||| 82 | |--|--|--|--|--| 83 | |6|5|||| 84 | 85 | stack1: 86 | 87 | |栈顶||||| 88 | |--|--|--|--|--| 89 | |*||||| 90 | 91 | 继续往后扫描,遇到操作符‘*’,优先级与栈顶‘*’优先级相同,因此弹出操作数并计算得到30入栈,最后‘*’入栈 92 | 93 | stack0: 94 | 95 | |栈顶||||| 96 | |--|--|--|--|--| 97 | |30||||| 98 | 99 | stack1: 100 | 101 | |栈顶||||| 102 | |--|--|--|--|--| 103 | |*||||| 104 | 105 | 继续扫描,‘8’入栈,操作符‘+’优先级小于‘*’,弹出操作数计算得到结果‘240’,并将其入栈,最后‘+’也入栈 106 | 107 | stack0: 108 | 109 | |栈顶||||| 110 | |--|--|--|--|--| 111 | |240||||| 112 | 113 | stack1: 114 | 115 | |栈顶||||| 116 | |--|--|--|--|--| 117 | |+||||| 118 | 119 | 最后‘5’入栈,发现操作符栈不为空,弹出操作符‘+’和两个操作数,并进行计算,得到‘245’,入栈,得到最终结果。 120 | stack0: 121 | 122 | |栈顶||||| 123 | |--|--|--|--|--| 124 | |245||||| 125 | 126 | stack1: 127 | 128 | |||||| 129 | |--|--|--|--|--| 130 | |||||| 131 | 132 | ## 代码实现 133 | ```c 134 | #include 135 | #include 136 | #define STACK_SIZE 64 /*栈大小*/ 137 | #define TOP_OF_STACK -1 /*栈顶位置*/ 138 | typedef int ElementType; /*栈元素类型*/ 139 | 140 | #define SUCCESS 0 141 | #define FAILURE -1 142 | 143 | /*定义栈结构*/ 144 | typedef struct StackInfo 145 | { 146 | int topOfStack; /*记录栈顶位置*/ 147 | ElementType stack[STACK_SIZE]; /*栈数组,也可以使用动态数组实现*/ 148 | }StackInfo_st; 149 | 150 | 151 | /*函数声明*/ 152 | int stack_push(StackInfo_st *s,ElementType value); 153 | int stack_pop(StackInfo_st *s,ElementType *value); 154 | int stack_top(StackInfo_st *s,ElementType *value); 155 | int stack_is_full(StackInfo_st *s); 156 | int stack_is_empty(StackInfo_st *s); 157 | 158 | 159 | /*入栈,0表示成,非0表示出错*/ 160 | int stack_push(StackInfo_st *s,ElementType value) 161 | { 162 | if(stack_is_full(s)) 163 | return FAILURE; 164 | /*先增加topOfStack,再赋值*/ 165 | s->topOfStack++; 166 | s->stack[s->topOfStack] = value; 167 | return SUCCESS; 168 | } 169 | 170 | /*出栈*/ 171 | int stack_pop(StackInfo_st *s,ElementType *value) 172 | { 173 | /*首先判断栈是否为空*/ 174 | if(stack_is_empty(s)) 175 | return FAILURE; 176 | *value = s->stack[s->topOfStack]; 177 | s->topOfStack--; 178 | return SUCCESS; 179 | } 180 | /*访问栈顶元素*/ 181 | int stack_top(StackInfo_st *s,ElementType *value) 182 | { 183 | /*首先判断栈是否为空*/ 184 | if(stack_is_empty(s)) 185 | { 186 | printf("stack is empty"); 187 | return FAILURE; 188 | } 189 | *value = s->stack[s->topOfStack]; 190 | return SUCCESS; 191 | } 192 | 193 | /*判断栈是否已满,满返回1,未满返回0*/ 194 | int stack_is_full(StackInfo_st *s) 195 | { 196 | return s->topOfStack == STACK_SIZE - 1; 197 | } 198 | /*判断栈是否为空,空返回1,非空返回0*/ 199 | int stack_is_empty(StackInfo_st *s) 200 | { 201 | return s->topOfStack == - 1; 202 | } 203 | 204 | /*用于记录符号的优先级,这里浪费了一些内存,可以优化*/ 205 | static char priority[128] = {0}; 206 | void priorityInit() 207 | { 208 | /*初始化优先级,值越小,优先级越高*/ 209 | priority['+'] = 4; 210 | priority['-'] = 4; 211 | priority['*'] = 3; 212 | priority['/'] = 3; 213 | priority['('] = 1; 214 | priority[')'] = 1; 215 | 216 | 217 | } 218 | /*比较运算符的优先级,op1优先级大于op2时,返回大于0的值*/ 219 | int priorityCompare(char op1,char op2) 220 | { 221 | return priority[op2] - priority[op1]; 222 | } 223 | /*出栈操作符和操作数进行计算*/ 224 | int calcOp(StackInfo_st *nums,StackInfo_st *ops,int nowOp) 225 | { 226 | int a ,b,op; 227 | stack_pop(ops,&op); 228 | printf("op %c is <= %c\n",nowOp,op); 229 | printf("get op from stack %c\n",op); 230 | if(SUCCESS != stack_pop(nums,&b)) 231 | { 232 | printf("pop failed\n"); 233 | return -1; 234 | } 235 | if(SUCCESS != stack_pop(nums,&a)) 236 | { 237 | printf("pop failed\n"); 238 | return 0; 239 | } 240 | printf("get b from stack %d\n",b); 241 | 242 | printf("get a from stack %d\n",a); 243 | switch(op) 244 | { 245 | case '+': 246 | { 247 | printf("push %d into stack\n",a+b); 248 | stack_push(nums,a+b); 249 | break; 250 | } 251 | case '-': 252 | { 253 | stack_push(nums,a-b); 254 | break; 255 | } 256 | case '*': 257 | { 258 | printf("push %d into stack\n",a*b); 259 | stack_push(nums,a*b); 260 | break; 261 | } 262 | case '/': 263 | { 264 | printf("push %d into stack\n",a/b); 265 | stack_push(nums,a/b); 266 | break; 267 | } 268 | } 269 | return 1; 270 | } 271 | int calc(const char* exp,int *result) 272 | { 273 | if(NULL == exp || NULL == result) 274 | return FAILURE; 275 | /*创建栈,用于保存数*/ 276 | StackInfo_st nums; 277 | nums.topOfStack = TOP_OF_STACK; 278 | 279 | /*用于保存操作符*/ 280 | StackInfo_st ops; 281 | ops.topOfStack = TOP_OF_STACK; 282 | int index = 0; 283 | /*用于标记,判断上一个是否为数字*/ 284 | int flag = 0; 285 | int temp = 0; 286 | int op ; 287 | while(0 != *exp) 288 | { 289 | /*如果是数字*/ 290 | if(isdigit(*exp)) 291 | { 292 | printf("char is %c\n",*exp); 293 | /*如果上一个还是数字,则取出栈顶数据*/ 294 | if(1 == flag) 295 | { 296 | 297 | stack_pop(&nums,&temp); 298 | printf("pop from stack num %d\n",temp); 299 | } 300 | else 301 | temp = 0; 302 | flag = 1; 303 | temp = 10 * temp + *exp-'0'; 304 | printf("push %d to stack\n",temp); 305 | stack_push(&nums,temp); 306 | } 307 | /*如果是操作符*/ 308 | else if('/' == *exp || '*' == *exp || '+' == *exp || '-' == *exp) 309 | { 310 | flag = 0; 311 | printf("OP is %c\n",*exp); 312 | while((ops.topOfStack > TOP_OF_STACK )&&(SUCCESS == stack_top(&ops,&op))&&'(' != op && ')'!=op&&(priorityCompare(*exp,op) < 0)) 313 | { 314 | calcOp(&nums, &ops,*exp); 315 | } 316 | printf("push %c to stack ops\n",*exp); 317 | stack_push(&ops,*exp); 318 | } 319 | /*左括号直接入栈*/ 320 | else if('(' == *exp ) 321 | { 322 | printf("push ( to stack ops\n"); 323 | flag = 0; 324 | stack_push(&ops,*exp); 325 | } 326 | /*右括号,计算*/ 327 | else if(')' ==*exp ) 328 | { 329 | printf("deal with ) in ops\n"); 330 | flag = 0; 331 | /*右括号时,不断计算,直到遇见左括号*/ 332 | while(SUCCESS == stack_top(&ops,&op) && '(' != op) 333 | { 334 | calcOp(&nums, &ops,*exp); 335 | } 336 | stack_pop(&ops,&op); 337 | } 338 | else 339 | { 340 | flag=0; 341 | } 342 | printf("flag is %d\n\n",flag); 343 | exp++; 344 | } 345 | /*计算剩余两个栈的内容*/ 346 | while((!stack_is_empty(&ops)) && (!stack_is_empty(&nums))) 347 | { 348 | if(!calcOp(&nums, &ops,0)) 349 | printf("exp is error\n"); 350 | } 351 | stack_pop(&nums,&temp); 352 | /*如果栈中还有内容,说明表达式错误*/ 353 | if((!stack_is_empty(&ops)) || (!stack_is_empty(&nums))) 354 | printf("\n\nexp is ok\n\n"); 355 | 356 | if(SUCCESS == stack_pop(&nums,&temp)) 357 | printf("result is %d\n",temp); 358 | *result = temp; 359 | return 0; 360 | } 361 | int main(int argc ,char *argv[]) 362 | { 363 | 364 | int result; 365 | priorityInit(); 366 | char exp[] = "6 * (2 + 3 * 3)* 8 + 5"; 367 | calc(exp,&result); 368 | printf("result is %d\n",result); 369 | return 0; 370 | } 371 | ``` 372 | 373 | ## 总结 374 | 本文介绍了利用栈对中缀表达式进行求值,而代码实现还有很多不足之处,例如对表达式的正确性校验不足,只能处理正整数等等,欢迎在此基础上完善补充。尽管如此,整个过程对使用栈进行中缀表达式的求值做了一个较为完整的介绍,因此具有一定的参考性。 375 | 376 | -------------------------------------------------------------------------------- /stack/栈-c语言实现.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 栈-c语言实现 3 | comments: true 4 | categories: 数据结构与算法 5 | tags: 6 | - C 7 | - 数据结构与算法 8 | - 栈 9 | abbrlink: 31765 10 | date: 2019-03-16 18:00:00 11 | --- 12 | ## 前言 13 | 栈是一种应用广泛的数据结构,例如函数的调用就需要使用栈,其实我们在介绍《[快速排序优化详解](https://www.yanbinghu.com/2019/02/21/28355.html)》的时候也使用到了栈结构。栈最鲜明的特点就是后进先出,一碟盘子就是类似这样的结构,最晚放上去的,可以最先拿出来。本文将介绍的是如何自己实现一个栈结构。 14 | 15 | ## 栈的操作 16 | 栈的常见操作有出栈(POP),从栈中弹出一个元素;入栈(PUSH),将一个元素压入栈中,访问栈顶元素(TOP),判断栈是否为空等。 17 | 18 | ## 栈的实现 19 | 栈是较容易实现的抽象数据结构之一。我们可以选择数组或者链表来实现,它们各有特点,前者容量有限且固定,但操作简单,而后者容量理论上不受限,但是操作并不如数组方便,每次入栈要进行内存申请,出栈要释放内存,稍有不慎便造成内存泄露。本文对两种实现都做介绍。 20 | 21 | ## 数组实现栈 22 | 用数组实现栈是比较容易的。这个时候的栈其实更像是访问受限的数组,数组可以通过下标访问,查找,插入等,但是栈只能从栈顶,或者说数组的末尾进行操作。我们只需要一个指针记录栈顶即可。有人可能问了,既然这里栈是访问受限的数组,为什么不直接使用数组呢?所谓能力越大,责任越大,而你暴露的越多,风险也越大就是如此。 23 | 24 | 我们来看一下数组实现栈的时候,栈的操作都是怎么实现的呢? 25 | 26 | #### 定义栈 27 | 用数组实现栈时是很容易定义的,只要定一个固定长度的数组即可,然后使用一个指针或者数组下标标记栈顶(topOfStack),栈为空时,它是-1: 28 | ```c 29 | #define STACK_SIZE 64 /*栈大小*/ 30 | #define TOP_OF_STACK -1 /*栈顶位置*/ 31 | typedef int ElementType /*栈元素类型*/ 32 | typedef struct StackInfo 33 | { 34 | int topOfStack; /*记录栈顶位置*/ 35 | ElementType stack[STACK_SIZE]; /*栈数组,也可以使用动态数组实现*/ 36 | }StackInfo_st; 37 | 38 | /*创建栈*/ 39 | StackInfo_st stack; 40 | stack.topOfStack = TOP_OF_STACK; 41 | ``` 42 | #### 入栈 43 | 入栈操作也很简单,只需要先将topOfStack加1,然后将元素放入数组即可。当然特别要注意检查此时栈是否已满。 44 | topOfStack = -1 45 | 46 | |||| 47 | |--|--|| 48 | |||| 49 | 50 | 将1入栈,此时topOfStack = 0, 51 | 52 | |topOfStack||| 53 | |--|--|| 54 | |1||| 55 | 56 | 代码实现: 57 | ```c 58 | #define SUCCESS 0 59 | #define FAILURE -1 60 | /*入栈,0表示成功,非0表示出错*/ 61 | int stack_push(StackInfo_st *s, ElementType value) 62 | { 63 | if(stack_is_full(s)) 64 | return FAILURE; 65 | /*先增加topOfStack,再赋值*/ 66 | s->topOfStack++; 67 | s->stack[s->topOfStack] = value; 68 | return SUCCESS; 69 | } 70 | ``` 71 | #### 出栈或访问栈顶元素 72 | 与入栈相反,先访问元素,然后将topOfStack减1,但是此时要注意检查栈是否已空。访问栈顶元素可直接使用下标访问,而不用将topOfStack减1。 73 | ```c 74 | /*出栈*/ 75 | int stack_pop(StackInfo_st *s,ElementType *value) 76 | { 77 | /*首先判断栈是否为空*/ 78 | if(stack_is_empty(s)) 79 | return FAILURE; 80 | *value = s->stack[s->topOfStack]; 81 | s->topOfStack--; 82 | return SUCCESS; 83 | } 84 | /*访问栈顶元素*/ 85 | int stack_top(StackInfo_st *s,ElementType *value); 86 | { 87 | /*首先判断栈是否为空*/ 88 | if(stack_is_empty(s)) 89 | return FAILURE; 90 | *value = s->stack[s->topOfStack]; 91 | return SUCCESS; 92 | } 93 | ``` 94 | #### 判断栈是否满 95 | 只要判断topOfStack与数组大小-1的大小即可。 96 | ```c 97 | /*判断栈是否已满,满返回1,未满返回0*/ 98 | int stack_is_full(StackInfo_st *s) 99 | { 100 | return s->topOfStack == STACK_SIZE - 1; 101 | } 102 | ``` 103 | #### 判断栈是否为空 104 | 只需要判断topOfStack是否小于等于-1即可。 105 | ```c 106 | /*判断栈是否为空,空返回1,非空返回0*/ 107 | int stack_is_empty(StackInfo_st *s) 108 | { 109 | return s->topOfStack == - 1; 110 | } 111 | ``` 112 | #### 完整可运行代码 113 | ```c 114 | #include 115 | 116 | #define STACK_SIZE 64 /*栈大小*/ 117 | #define TOP_OF_STACK -1 /*栈顶位置*/ 118 | typedef int ElementType; /*栈元素类型*/ 119 | 120 | #define SUCCESS 0 121 | #define FAILURE -1 122 | 123 | /*定义栈结构*/ 124 | typedef struct StackInfo 125 | { 126 | int topOfStack; /*记录栈顶位置*/ 127 | ElementType stack[STACK_SIZE]; /*栈数组,也可以使用动态数组实现*/ 128 | }StackInfo_st; 129 | 130 | 131 | /*函数声明*/ 132 | int stack_push(StackInfo_st *s,ElementType value); 133 | int stack_pop(StackInfo_st *s,ElementType *value); 134 | int stack_top(StackInfo_st *s,ElementType *value); 135 | int stack_is_full(StackInfo_st *s); 136 | int stack_is_empty(StackInfo_st *s); 137 | 138 | 139 | /*入栈,0表示成,非0表示出错*/ 140 | int stack_push(StackInfo_st *s,ElementType value) 141 | { 142 | if(stack_is_full(s)) 143 | return FAILURE; 144 | /*先增加topOfStack,再赋值*/ 145 | s->topOfStack++; 146 | s->stack[s->topOfStack] = value; 147 | return SUCCESS; 148 | } 149 | 150 | /*出栈*/ 151 | int stack_pop(StackInfo_st *s,ElementType *value) 152 | { 153 | /*首先判断栈是否为空*/ 154 | if(stack_is_empty(s)) 155 | return FAILURE; 156 | *value = s->stack[s->topOfStack]; 157 | s->topOfStack--; 158 | return SUCCESS; 159 | } 160 | 161 | /*访问栈顶元素*/ 162 | int stack_top(StackInfo_st *s,ElementType *value) 163 | { 164 | /*首先判断栈是否为空*/ 165 | if(stack_is_empty(s)) 166 | return FAILURE; 167 | *value = s->stack[s->topOfStack]; 168 | return SUCCESS; 169 | } 170 | 171 | /*判断栈是否已满,满返回1,未满返回0*/ 172 | int stack_is_full(StackInfo_st *s) 173 | { 174 | return s->topOfStack == STACK_SIZE - 1; 175 | } 176 | 177 | /*判断栈是否为空,空返回1,非空返回0*/ 178 | int stack_is_empty(StackInfo_st *s) 179 | { 180 | return s->topOfStack == - 1; 181 | } 182 | int main(void) 183 | { 184 | 185 | /*创建栈*/ 186 | StackInfo_st stack; 187 | stack.topOfStack = TOP_OF_STACK; 188 | 189 | /*如果栈为空,则压入元素1*/ 190 | if(stack_is_empty(&stack)) 191 | { 192 | printf("push value 1\n"); 193 | stack_push(&stack,1); 194 | } 195 | 196 | /*访问栈顶元素*/ 197 | int topVal; 198 | stack_top(&stack, &topVal); 199 | printf("top value %d\n",topVal); 200 | 201 | /*出栈*/ 202 | int popVal; 203 | stack_pop(&stack, &popVal); 204 | printf("pop value %d\n",popVal); 205 | int i = 0; 206 | while(SUCCESS == stack_push(&stack,i)) 207 | { 208 | i++; 209 | } 210 | printf("stack is full,topOfStack is %d\n",stack.topOfStack); 211 | 212 | return 0; 213 | } 214 | ``` 215 | 运行结果: 216 | ``` 217 | push value 1 218 | top value 1 219 | pop value 1 220 | stack is full,topOfStack is 63 221 | ``` 222 | ## 链表实现栈 223 | 与数组实现栈不一样的地方是,链式栈可以动态扩容,基本没有长度限制(受限于内存)。另外,它在入栈以及出栈的时候需要申请或者释放内存。 224 | #### 创建栈 225 | 创建栈很容易,只需要声明一个头指针即可,它的next指针指向栈顶,初始时为空: 226 | ```c 227 | /*定义栈结构*/ 228 | typedef struct StackInfo 229 | { 230 | ElementType value; /*记录栈顶位置*/ 231 | struct StackInfo *next; /*指向栈的下一个元素*/ 232 | }StackInfo_st; 233 | 234 | /*创建栈,外部释放内存*/ 235 | StackInfo_st *createStack(void) 236 | { 237 | StackInfo_st *stack = malloc(sizeof(StackInfo_st)); 238 | if(NULL == stack) 239 | { 240 | printf("malloc failed\n"); 241 | return NULL; 242 | } 243 | /*stack-next为栈顶指针*/ 244 | stack->next = NULL; 245 | return stack; 246 | } 247 | ``` 248 | 249 | #### 入栈 250 | 入栈只需要为新的元素申请内存空间,并将栈顶指针指向新的节点即可。 251 | ![入栈操作](https://github.com/yanbinghu/BlogImages/raw/master/articles/%E6%A0%88-C%E8%AF%AD%E8%A8%80%E5%AE%9E%E7%8E%B0/stack_push.png) 252 | ```c 253 | /*入栈,0表示成,非0表示出错*/ 254 | int stack_push(StackInfo_st *s,ElementType value) 255 | { 256 | StackInfo_st *temp = malloc(sizeof(StackInfo_st)); 257 | if(NULL == temp) 258 | { 259 | printf("malloc failed\n"); 260 | return FAILURE; 261 | } 262 | /*将新的节点添加s->next前,使得s->next永远指向栈顶*/ 263 | temp->value = value; 264 | temp->next = s->next; 265 | s->next = temp; 266 | return SUCCESS; 267 | } 268 | ``` 269 | 270 | #### 出栈或访问栈顶元素 271 | 出栈时,将栈顶指针指向下下个节点,返回元素值,并释放栈顶指针下个节点的内存。而访问栈顶元素只需要返回栈顶指针指向节点的元素值即可。 272 | ![出栈](https://github.com/yanbinghu/BlogImages/raw/master/articles/%E6%A0%88-C%E8%AF%AD%E8%A8%80%E5%AE%9E%E7%8E%B0/stack_pop.png) 273 | ```c 274 | /*出栈*/ 275 | int stack_pop(StackInfo_st *s,ElementType *value) 276 | { 277 | /*首先判断栈是否为空*/ 278 | if(stack_is_empty(s)) 279 | return FAILURE; 280 | 281 | /*找出栈顶元素*/ 282 | *value = s->next->value; 283 | StackInfo_st *temp = s->next; 284 | s->next = s->next->next; 285 | 286 | /*释放栈顶节点内存*/ 287 | free(temp); 288 | temp = NULL; 289 | 290 | return SUCCESS; 291 | } 292 | /*访问栈顶元素*/ 293 | int stack_top(StackInfo_st *s,ElementType *value) 294 | { 295 | /*首先判断栈是否为空*/ 296 | if(stack_is_empty(s)) 297 | return FAILURE; 298 | *value = s->next->value; 299 | return SUCCESS; 300 | } 301 | ``` 302 | 303 | #### 判断栈是否为空 304 | 判断栈空也很简单,只需要判断栈顶指针是否为空即可。 305 | ```c 306 | /*判断栈是否为空,空返回1,未空返回0*/ 307 | int stack_is_empty(StackInfo_st *s) 308 | { 309 | /*栈顶指针为空,则栈为空*/ 310 | return s->next == NULL; 311 | } 312 | ``` 313 | ## 完整可运行代码 314 | ```c 315 | #include 316 | #include 317 | typedef int ElementType; /*栈元素类型*/ 318 | 319 | #define SUCCESS 0 320 | #define FAILURE -1 321 | 322 | /*定义栈结构*/ 323 | typedef struct StackInfo 324 | { 325 | ElementType value; /*记录栈顶位置*/ 326 | struct StackInfo *next; /*指向栈的下一个元素*/ 327 | }StackInfo_st; 328 | 329 | 330 | /*函数声明*/ 331 | StackInfo_st *createStack(void); 332 | int stack_push(StackInfo_st *s,ElementType value); 333 | int stack_pop(StackInfo_st *s,ElementType *value); 334 | int stack_top(StackInfo_st *s,ElementType *value); 335 | int stack_is_empty(StackInfo_st *s); 336 | 337 | /*创建栈,外部释放内存*/ 338 | StackInfo_st *createStack(void) 339 | { 340 | StackInfo_st *stack = malloc(sizeof(StackInfo_st)); 341 | if(NULL == stack) 342 | { 343 | printf("malloc failed\n"); 344 | return NULL; 345 | } 346 | stack->next = NULL; 347 | return stack; 348 | } 349 | /*入栈,0表示成,非0表示出错*/ 350 | int stack_push(StackInfo_st *s,ElementType value) 351 | { 352 | StackInfo_st *temp = malloc(sizeof(StackInfo_st)); 353 | if(NULL == temp) 354 | { 355 | printf("malloc failed\n"); 356 | return FAILURE; 357 | } 358 | /*将新的节点添加s->next前,使得s->next永远指向栈顶*/ 359 | temp->value = value; 360 | temp->next = s->next; 361 | s->next = temp; 362 | return SUCCESS; 363 | } 364 | 365 | /*出栈*/ 366 | int stack_pop(StackInfo_st *s,ElementType *value) 367 | { 368 | /*首先判断栈是否为空*/ 369 | if(stack_is_empty(s)) 370 | return FAILURE; 371 | 372 | /*找出栈顶元素*/ 373 | *value = s->next->value; 374 | StackInfo_st *temp = s->next; 375 | s->next = s->next->next; 376 | 377 | /*释放栈顶节点内存*/ 378 | free(temp); 379 | temp = NULL; 380 | 381 | return SUCCESS; 382 | } 383 | /*访问栈顶元素*/ 384 | int stack_top(StackInfo_st *s,ElementType *value) 385 | { 386 | /*首先判断栈是否为空*/ 387 | if(stack_is_empty(s)) 388 | return FAILURE; 389 | *value = s->next->value; 390 | return SUCCESS; 391 | } 392 | 393 | /*判断栈是否为空,空返回1,未空返回0*/ 394 | int stack_is_empty(StackInfo_st *s) 395 | { 396 | /*栈顶指针为空,则栈为空*/ 397 | return s->next == NULL; 398 | } 399 | 400 | int main(void) 401 | { 402 | 403 | /*创建栈*/ 404 | StackInfo_st *stack = createStack(); 405 | 406 | /*如果栈为空,则压入元素1*/ 407 | if(stack_is_empty(stack)) 408 | { 409 | printf("push value 1\n"); 410 | stack_push(stack,1); 411 | } 412 | 413 | /*访问栈顶元素*/ 414 | int topVal; 415 | stack_top(stack, &topVal); 416 | printf("top value %d\n",topVal); 417 | 418 | /*出栈*/ 419 | int popVal; 420 | stack_pop(stack, &popVal); 421 | printf("pop value %d\n",popVal); 422 | int i = 0; 423 | while(SUCCESS == stack_push(stack,i) && i < 5) 424 | { 425 | i++; 426 | } 427 | printf("top if stack value is %d\n",stack->next->value); 428 | /*最后记得将栈内存都释放,可自己尝试实现*/ 429 | return 0; 430 | } 431 | ``` 432 | 运行结果: 433 | ``` 434 | push value 1 435 | top value 1 436 | pop value 1 437 | top if stack value is 5 438 | ``` 439 | ## 总结 440 | 本文介绍了栈的基本操作以及栈的基本实现。后面将会介绍一些栈的具体应用。 441 | 442 | ## 思考 443 | 还记得如何使用GDB查看链表内容吗?参考《[GDB调试指南-变量查看](https://www.yanbinghu.com/2019/03/10/50132.html)》。 444 | -------------------------------------------------------------------------------- /sort/快速排序.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 快速排序优化详解 3 | comments: true 4 | categories: 数据结构与算法 5 | tags: 6 | - C 7 | - 数据结构与算法 8 | abbrlink: 28355 9 | date: 2019-02-21 21:10:00 10 | --- 11 | 正如它的名字所体现,快速排序是在实践中最快的已知排序算法,平均运行时间为O(NlogN),最坏的运行时间为O(N^2)。算法的基本思想很简单,然而想要写出一个**高效**的快速排序算法并不是那么简单。基准的选择,元素的分割等都至关重要,如果你不清楚如何优化快速排序算法,本文你不该错过。 12 | 13 | ## 算法思想 14 | 快速排序利用了分治的策略。而分治的基本基本思想是:将原问题划分为若干与原问题类似子问题,解决这些子问题,将子问题的解组成原问题的解。 15 | 16 | 那么如何利用分治的思想对数据进行排序呢?假如有一个元素集合A: 17 | + 选择A中的任意一个元素pivot,该元素作为基准 18 | + 将小于基准的元素移到左边,大于基准的元素移到右边(分区操作) 19 | + A被pivot分为两部分,继续对剩下的两部分做同样的处理 20 | + 直到所有子集元素不再需要进行上述步骤 21 | 22 | 可以看到算法思想比较简单,然而上述步骤实际又该如何处理呢? 23 | 24 | ## 如何选择基准 25 | 实际上无论怎么选择基准,都不会影响排序结果,**但是不同的选择却可能影响整体排序时间**,因为基准选择不同,会导致分割的两个集合大小不同,如果分割之后,两个集合大小是几乎相等的,那么我们整体分割的次数显然也会减少,这样整体耗费的时间也相应降低。我们来看一下有哪些可选择策略。 26 | 27 | #### 选择第一个或者最后一个 28 | 如果待排序数是随机的,那么选择第一个或者最后一个作基准是没有什么问题的,这也是我们最常见到的选择方案。但如果待排序数据已经排好序的,就**会产生一个很糟糕的分割**。几乎所有的数据都被分割到一个集合中,而另一个集合没有数据。这样的情况下,**时间花费了,却没有做太多实事**。而它的时间复杂度就是最差的情况O(N^2)。**因此这种策略是绝对不推荐的**。 29 | #### 随机选择 30 | 随机选择基准是一种比较安全的做法。因为它不会总是产生劣质的分割。 31 | C语言实现参考: 32 | ```c 33 | ElementType randomPivot(ElementType A[],int start,int end) 34 | { 35 | srand(time(NULL)) 36 | int randIndex = rand()%(end -start)+start; 37 | return A[randIndex]; 38 | } 39 | ``` 40 | #### 选择三数中值 41 | 从前面的描述我们知道,如果能够选择到数据的中值,那是最好的,因为它能够将集合近乎等分为二。但是很多时候很难算出中值,并且会耗费计算时间。因此我们随机选取三个元素,并用它们的中值作为整个数据中值的估计值。在这里,我们选择最左端,最右端和中间位置的三个元素的中值作为基准。 42 | 43 | 假如有以下数组: 44 | ``` 45 | 1 9 10 3 8 7 6 2 4 46 | ``` 47 | 左端元素为1,位置为0,右端元素为4,位置为8,则中间位置为[0+8]/2=4,中间元素为8。那么三数中值就为4(1,4,8的中值)。 48 | ## 如何将元素移动到基准两侧 49 | 选好基准之后,如何将元素移动到基准两侧呢?通常的做法如下: 50 | + 将基准元素与最后的元素交换,使得基准元素不在被分割的数据范围 51 | + i和j分别从第一个元素和倒数第二个元素开始。i在j的左边时,将i右移,直到发现大于等于基准的元素,然后将j左移,直到发现小于等于基准的元素。i和j停止时,元素互换。这样就把大于等于基准的移到了右边,小于等于基准的移到了左边 52 | + 重复上面的步骤,直到i和j交错 53 | + 将基准元素与i所指向的元素交换,使得基准元素将整个元素集合分割为小于基准和大于基准的元素集合 54 | 55 | **在们采用三数中值得方法选择基准的情况下**,既然基准是中值,实际上只要保证左端,右端,中间值是从小到大即可。还是以前面提到的数组为例,我们找到三者后,对三者进行排序如下: 56 | 排序前 57 | 58 | |↓||||↓||||↓| 59 | |--|--|--|--|--|--|--|--|--| 60 | |1|9|10|3|8|7|6|2|4| 61 | 62 | 排序后 63 | 64 | |↓||||↓||||↓| 65 | |--|--|--|--|--|--|--|--|--| 66 | |1|9|10|3|4|7|6|2|8| 67 | 68 | 如果是这样的情况,那么实际上不需要把基准元素和最后一个元素交换,而只需要和倒数第二个元素交换即可,因为最后一个元素肯定大于基准,这样可以**减少交换次数**。 69 | 70 | 如果前面的描述还不清楚,我们看一看实际中一趟完整的流程是什么样的。 71 | 72 | 第一步,将左端,右端和中间值排序,中值作为基准: 73 | 74 | |↓||||↓||||↓| 75 | |--|--|--|--|--|--|--|--|--| 76 | | 1 | 9 | 10 | 3 | 4 | 7 | 6 | 2 | 8 | 77 | | | | | |基准| | | | | 78 | 79 | 第二步,将中值与倒数第二个数交换位置: 80 | 81 | |||||交|换|位|置|| 82 | |--|--|--|--|--|--|--|--|--| 83 | |||||↓|||↓|| 84 | |1|9|10|3|2|7|6|4|8| 85 | ||||||||基准|| 86 | 87 | 第三步,i向右移动,直到发现大于等于基准的元素9: 88 | 89 | |||||||||| 90 | |--|--|--|--|--|--|--|--|--| 91 | |1|9|10|3|2|7|6|4|8| 92 | ||↑|||||↑|↑|| 93 | ||**i**|||||**j**|基准|| 94 | 95 | 第四步,j向左移动,直到发现小于等于基准的元素2: 96 | 97 | |||||||||| 98 | |--|--|--|--|--|--|--|--|--| 99 | |1|9|10|3|2|7|6|4|8| 100 | ||↑|||↑|||↑|| 101 | ||**i**|||**j**|||基准|| 102 | 103 | 第五步,交换i和j: 104 | 105 | |||||||||| 106 | |--|--|--|--|--|--|--|--|--| 107 | |1|2|10|3|9|7|6|4|8| 108 | ||↑|||↑|||↑|| 109 | ||**i**|交|换|**j**|||基准|| 110 | 111 | 第六步,重复上述步骤,i右移,j左移: 112 | 113 | |||||||||| 114 | |--|--|--|--|--|--|--|--|--| 115 | |1|2|10|3|9|7|6|4|8| 116 | |||↑|↑||||↑|| 117 | |||**i**|**j**||||基准|| 118 | 119 | 第七步,交换i和j指向的值: 120 | 121 | |||||||||| 122 | |--|--|--|--|--|--|--|--|--| 123 | |1|2|3|10|9|7|6|4|8| 124 | |||↑|↑||||↑|| 125 | |||**i**|**j**||||基准|| 126 | 127 | 第八步,重复上述步骤,i右移,j左移,此时i和j已经交错: 128 | 129 | |||||||||| 130 | |--|--|--|--|--|--|--|--|--| 131 | |1|2|3|10|9|7|6|4|8| 132 | |||↑|↑||||↑|| 133 | |||**j**|**i**||||基准|| 134 | 135 | 第九步,i和j已经交错,因此最后将基准元素与i所指元素交换: 136 | 137 | |||||||||| 138 | |--|--|--|--|--|--|--|--|--| 139 | |1|2|3|4|9|7|6|10|8| 140 | ||||↑|||||| 141 | ||||**i**|||||| 142 | 143 | 到这一步的时候,我们发现i的左边都是小于i指向的元素,而右边都是大于i的元素。最后在对子集进行同样的操作即可。 144 | 145 | ## 如何对子集进行排序 146 | #### 递归法 147 | 最常见的便是递归法了。递归的好处是代码简洁易懂,但是不可忽略的是,当递归嵌套过深时,它的效率问题以及栈溢出的风险**可能**会迫使你选择非递归法。在前面对整个集合一分为二之后,对剩下的两个集合递归调用,直到完成排序。简单描述如下(非可运行代码): 148 | ```c 149 | void Qsort(int A[],int left,int right) 150 | { 151 | /*分区操作*/ 152 | int i = partition(A,left,right); 153 | /*对子集递归调用*/ 154 | Qsort(A,left,i-1); 155 | Qsort(A,i+1,right); 156 | } 157 | ``` 158 | 递归最需要注意的便是递归结束调用,否则会产生无限递归,从而发生栈溢出。 159 | 160 | 后面我们会看到,递归法的代码非常简洁。(相关阅读《[重新看递归](https://www.yanbinghu.com/2019/01/07/16863.html)》) 161 | ## 尾递归 162 | 在递归版本中,Qsort分别递归调用计算左右两个子集合,而第二个递归其实并非必须,完全可以用循环来替代,以下代码模拟实现了尾递归,(并非是真的尾递归): 163 | ```c 164 | void Qsort(ElementType A[],int left,int right) 165 | { 166 | int i = 0; 167 | while(left < right) 168 | { 169 | /*分割操作*/ 170 | i = partition(A,left,right); 171 | /*递归调用*/ 172 | Qsort(A,left,i-1); 173 | /*右半部分通过循环实现*/ 174 | left = i + 1; 175 | } 176 | } 177 | ``` 178 | ## 非递归法 179 | 那么有没有方法可以不用递归呢?既然递归每次都进行压栈操作,那么我们能不能分区后仅仅将区间信息存储到栈里,然后从栈中取出区间再继续分区呢?显然是可以的。实际上我们每次分区时,只需要知道区间即可,那么将这些区间信息存储起来,就可以不用递归了,按照分好的区间不断分区即可。 180 | 181 | 例如对于前面提到的数组,首先对区间[0,8]进行分区操作,之后得到两个新的分区,1,2,3和9,7,6,10,8,假设两个区间仍然可以使用快速排序,那么需要将区间[0,2]和[5,8]的其中一个压栈,另一个继续分区操作。 182 | 183 | 按照这种思路,代码简单描述如下(非可运行代码): 184 | ```c 185 | void Qsort(A,left,right) 186 | { 187 | while(STACK_IS_NOT_EMPTY) 188 | { 189 | /*分区操作*/ 190 | POP(lo,hi); 191 | int mid = partition(A,lo,hi); 192 | /*存储新的边界*/ 193 | PUSH(lo,mid-1); 194 | PUSH(mid+1,hi); 195 | } 196 | } 197 | ``` 198 | 当然这里面没有体现分区终止条件。我们需要在数据量小于一定值的时候,就不再继续进行分区操作了,而是选择插入排序(为什么?)。 199 | 200 | 那么问题来了,如何选择栈的大小呢?查看qsort.c的源码发现,它选择了如下的值: 201 | ```c 202 | #define STACK_SIZE (8* sizeof(unsigned long int)); 203 | ``` 204 | 为什么会是这个值呢?设想一下,假设待排序数组长度使用unsigned long int来表示,并且假设每次都将集合分为二等分。那么即便数组长度达到最大值,实际上最多只需要分割8 *(sizeof(unsigned long int))次,也就将它分割完了。然而由于以下几个原因,需要存储在栈中的区间信息很难超出栈空间,因为: 205 | + 数组长度不会接近unsigned long int,否则内存也撑不住了 206 | + 区间足够小时,不采用快速排序 207 | + 每做一个分区,只会增加一个区间PUSH到栈中,增长速度慢 208 | 209 | ## 注意事项 210 | 至此,快速排序所有的主要步骤已经介绍完毕。但是有以下注意事项: 211 | + 有大量重复元素时避免产生糟糕分区,因此在发现大于等于基准或者小于等于基准时,便停止扫描。 212 | + 通常会将基准一开始移动到最后位置或倒数第二个位置,避免基准在待分区区间。 213 | + 对于很小的数组(N<=20),插入排序要比快速排序更好。因为快速排序有递归开销,并且插入排序是稳定排序。 214 | + 如果函数本身的局部变量很少,那么递归带来的开销也就越小;如果递归发生栈溢出了,首先需要排除代码设计问题。因此如果你设计的非递归版本效率低于递归版本,也不要惊讶。 215 | 216 | 注:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。--来自百科 217 | 218 | ## 递归版代码实现 219 | C语言代码实现如下: 220 | ```c 221 | #include 222 | #include 223 | #include 224 | /*使用快速排序的区间大小临界值,可根据实际情况选择*/ 225 | #define MAX_THRESH 4 226 | typedef int ElementType; 227 | /*元素交换*/ 228 | void swap(ElementType *a,ElementType *b) 229 | { 230 | ElementType temp = *a; 231 | *a = *b; 232 | *b = temp; 233 | } 234 | /*插入排序*/ 235 | void insertSort(ElementType A[],int N) 236 | { 237 | /*优化后的插入排序*/ 238 | int j = 0; 239 | int p = 0; 240 | int temp = 0; 241 | for(p = 1;p0 && A[j-1] > temp;j--) 245 | { 246 | A[j] = A[j-1]; 247 | } 248 | A[j] = temp; 249 | } 250 | 251 | } 252 | /*三数中值法选择基准*/ 253 | ElementType medianPivot(ElementType A[],int left,int right) 254 | { 255 | int center = (left + right)/2 ; 256 | /*对三数进行排序*/ 257 | if(A[left] > A[center]) 258 | swap(&A[left],&A[center]); 259 | if(A[left] > A[right]) 260 | swap(&A[left],&A[right]); 261 | if(A[center] > A[right]) 262 | swap(&A[center],&A[right]); 263 | 264 | /*交换中值和倒数第二个元素*/ 265 | swap(&A[center],&A[right-1]); 266 | return A[right-1]; 267 | } 268 | 269 | /*分区操作*/ 270 | int partition(ElementType A[],int left,int right) 271 | { 272 | 273 | int i = left; 274 | int j = right-1; 275 | /*获取基准值*/ 276 | ElementType pivot = medianPivot(A,left,right); 277 | for(;;) 278 | { 279 | /*i j分别向右和向左移动,为什么不直接先i++?*/ 280 | while(A[++i] < pivot) 281 | {} 282 | while(A[--j] > pivot) 283 | {} 284 | 285 | if(i < j) 286 | { 287 | swap(&A[i],&A[j]); 288 | } 289 | /*交错时停止*/ 290 | else 291 | { 292 | break; 293 | } 294 | 295 | } 296 | /*交换基准元素和i指向的元素*/ 297 | swap(&A[i],&A[right-1]); 298 | return i; 299 | 300 | } 301 | 302 | 303 | void Qsort(ElementType A[],int left,int right) 304 | { 305 | int i = 0; 306 | register ElementType *arr = A; 307 | if(right-left >= MAX_THRESH) 308 | { 309 | /*分割操作*/ 310 | i = partition(arr,left,right); 311 | /*递归*/ 312 | Qsort(arr,left,i-1); 313 | Qsort(arr,i+1,right); 314 | } 315 | else 316 | { 317 | /*数据量较小时,使用插入排序*/ 318 | insertSort(arr+left,right-left+1); 319 | } 320 | } 321 | /*打印数组内容*/ 322 | void printArray(ElementType A[],int n) 323 | { 324 | if(n > 100) 325 | { 326 | printf("too much,will not print\n"); 327 | return; 328 | } 329 | int i = 0; 330 | while(i < n) 331 | { 332 | printf("%d ",A[i]); 333 | i++; 334 | } 335 | printf("\n"); 336 | } 337 | int main(int argc,char *argv[]) 338 | { 339 | if(argc < 2) 340 | { 341 | printf("usage:qsort num\n"); 342 | return -1; 343 | } 344 | int len = atoi(argv[1]); 345 | printf("sort for %d numbers\n",len); 346 | /*随机产生输入数量的数据*/ 347 | int *A = malloc(sizeof(int)*len); 348 | int i = 0; 349 | srand(time(NULL)); 350 | while(i < len) 351 | { 352 | A[i] = rand(); 353 | i++; 354 | } 355 | printf("before sort:"); 356 | printArray(A,len); 357 | /*对数据进行排序*/ 358 | Qsort(A,0,len-1); 359 | printf("after sort:"); 360 | printArray(A,len); 361 | return 0; 362 | } 363 | ``` 364 | ## 尾递归版代码实现 365 | 略。 366 | ## 非递归版代码实现 367 | 非递归版与递归版大部分代码相同,Qsort函数有所不同,并且增加栈相关内容定义: 368 | ```c 369 | /*存储区间信息*/ 370 | typedef struct stack_node_t 371 | { 372 | int lo; 373 | int hi; 374 | }struct_node; 375 | /*最大栈长度*/ 376 | #define STACK_SIZE 8 * sizeof(unsigned int) 377 | 378 | /*入栈,出栈*/ 379 | #define STACK_PUSH( low, hig ) ( (top->lo = low), (top->hi = hig), top++) 380 | #define STACK_POP( low, hig ) (top--, (low = top->lo), (hig = top->hi) ) 381 | 382 | /*快速排序*/ 383 | void Qsort( ElementType A[], int left, int right ) 384 | { 385 | if(NULL == A) 386 | return; 387 | /*使用寄存器指针*/ 388 | register ElementType *arr = A; 389 | if ( right - left >= MAX_THRESH ) 390 | { 391 | struct_node stack[STACK_SIZE] = { { 0 } }; 392 | register struct_node *top = stack; 393 | 394 | /*最大区间压栈*/ 395 | int lo = left; 396 | int hi = right; 397 | STACK_PUSH( 0, 0); 398 | int mid = 0; 399 | while ( stack < top ) 400 | { 401 | /*出栈,取出一个区间进行分区操作*/ 402 | 403 | mid = partition( arr, lo, hi ); 404 | 405 | /*分情况处理,左边小于阈值*/ 406 | if ( (mid - 1 - lo) <= MAX_THRESH) 407 | { 408 | /* 左右两个数据段的元素都小于阈值,取出栈中数据段进行划分*/ 409 | if ( (hi - (mid+1)) <= MAX_THRESH) 410 | /* 都小于阈值,从栈中取出数据段 */ 411 | STACK_POP (lo, hi); 412 | else 413 | /* 只有右边大于阈值,右边继续分区*/ 414 | lo = mid -1 ; 415 | } 416 | /*右边小于阈值,继续计算左边*/ 417 | else if ((hi - (mid+1)) <= MAX_THRESH) 418 | hi = mid - 1; 419 | /*左右两边都大于阈值,且左边大于右边,左边入栈,右边继续分区*/ 420 | else if ((mid -1 - lo) > (hi - (mid + 1))) 421 | { 422 | STACK_PUSH (lo, mid - 1); 423 | lo = mid + 1; 424 | } 425 | /*左右两边都大于阈值,且右边大于左边,右边入栈,左边继续分区*/ 426 | else 427 | { 428 | STACK_PUSH (mid + 1, hi); 429 | hi = mid -1; 430 | } 431 | } 432 | 433 | } 434 | 435 | /*最后再使用插入排序,对于接近有序状态的数据,插入排序速度很快*/ 436 | insertSort(arr,right-left+1); 437 | 438 | } 439 | ``` 440 | 441 | ## 运行结果 442 | 我们随机产生1亿个整数,并对其进行排序: 443 | ``` 444 | $ gcc -o qsort qsort.c 445 | $ time ./qsort 100000000 446 | ``` 447 | 递归版运行结果: 448 | ``` 449 | sort for 100000000 numbers 450 | before sort:too much,will not print 451 | after sort:too much,will not print 452 | 453 | real 0m16.753s 454 | user 0m16.623s 455 | sys 0m0.132s 456 | ``` 457 | 非递归版结果: 458 | ``` 459 | sort for 100000000 numbers 460 | before sort:too much,will not print 461 | after sort:too much,will not print 462 | 463 | real 0m16.556s 464 | user 0m16.421s 465 | sys 0m0.137s 466 | ``` 467 | 可以看到,实际上两种方法的效率差距并不是很大。至于原因,前面我们已经说过了。 468 | ## 总结 469 | 本文所写的示例实现与glibc的实现相比,还有很多可优化的地方,例如,本文实现仅对int类型实现了排序或交换值,如果待排序内容是其他类型,就显得力不从心,读者可参考《高级指针话题函数指针》思考如何实现对任意数据类型进行排序,。但快速排序的优化主要从以下几个方面考虑: 470 | + 优化基准选择 471 | + 优化小数组排序效率 472 | + 优化交换次数 473 | + 优化递归 474 | + 优化最差情况,避免糟糕分区 475 | + 元素聚合 476 | 477 | 有兴趣地也可以进一步阅读qsort源码,进一步了解其中丧心病狂的优化。 478 | ## 思考 479 | + 为什么要在遇到相同元素时就进行扫描? 480 | + 插入排序最好的情况时间复杂度是多少,在什么情况下出现? 481 | + 文中实现的代码还有哪些可以优化的地方? 482 | 483 | ## 练习 484 | + 采用第一种基准选择策略实现快速排序,并测试对有序数组的排序性能 485 | + 实现通用快速排序算法,参考《[C语言函数指针](https://www.yanbinghu.com/2019/01/03/3593.html)》 486 | 487 | 488 | ## 参考 489 | + 《数据结构与算法分析》 490 | + 《算法导论》 491 | + glibc qsort.c源码 492 | 493 | 494 | 495 | --------------------------------------------------------------------------------