基本概念 数据结构: 相互之间存在一种或者多种特定关系的数据元素的集合。在逻辑上可以分为线性结构,散列结构,树形结构,图形结构 等等。
算法: 求解具体问题的步骤描述,代码上表现的是解决特定问题的一组有限的指令序列。
算法复杂度 :时间和空间复杂度 ,衡量算法效率,算法在执行过程中,随着数据规模n的增长,算法执行所花费的时间和空间的增长速度 。
常见的时间复杂度 :
大O计法
应用实例
O(1)
数组随机访问,哈希表
O(logn)
二分搜素,二叉堆调整,AVL,红黑树查找
O(n)
线性搜索
O(nlogn)
堆排序,快速排序,归并排序
O(n^2)
冒泡排序,选择排序,插入排序
O(2^n)
子集树
O(n!)
排列树
常见算法的时间复杂度关系:O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(2^n)<O(n!)<O(n^n)
线性表 数组
特点:内存是连续的。
优点:
下标访问(随机访问)时间复杂度是O(1)
末尾位置增加/删除元素时间复杂度是O(1)
访问元素前后相邻位置的元素非常方便。
缺点:
非末尾位置增加/删除元素需要进行大量的数据移动,时间复杂度O(n)
搜索的时间复杂度:当是无序数组时,线性搜索O(n)。当是有序数组时,二分搜索O(logn)
数组扩容消耗比较大(这里需要解释),看下列扩容代码
数组本身及基本功能实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 #include <iostream> #include <time.h> class Array { public : Array (int size = 10 ) :mCur (0 ), mCap (size) { mpArr = new int [mCap](); } ~Array () { delete [] mpArr; mpArr = nullptr ; } void push_back (int val) { if (mCur == mCap) { expand (2 * mCap); } mpArr[mCur++] = val; } void pop_back () { if (mCur == 0 ) { return ; } mCur--; } void insert (int pos, int val) { if (pos<0 || pos>mCap) { return ; } if (mCur == mCap) { expand (2 * mCap); } for (int i = mCur - 1 ;i >= pos;i--) { mpArr[i + 1 ] = mpArr[i]; } mpArr[pos] = val; mCur++; } void erase (int pos) { if (pos<0 || pos>=mCur) { return ; } for (int i = pos + 1 ;i < mCur;i++) { mpArr[i - 1 ] = mpArr[i]; } mCur--; } int find (int val) { for (int i = 0 ;i < mCur;i++) { if (mpArr[i] == val) { return i; } } return -1 ; } void show () const { for (int i = 0 ;i < mCur;i++) { std::cout << mpArr[i] << " " ; } std::cout << std::endl; } private : int * mpArr; int mCap; int mCur; void expand (int size) { int * p = new int [size]; memcpy (p, mpArr, sizeof (int ) * mCap); delete []mpArr; mpArr = p; mCap = size; } }; int main () { Array arr; srand (time (0 )); for (int i = 0 ;i < 10 ;i++) { arr.push_back (rand () % 100 ); } arr.show (); arr.pop_back (); arr.show (); arr.insert (0 , 100 ); arr.show (); arr.insert (10 , 200 ); arr.show (); int pos = arr.find (100 ); if (pos != -1 ) { arr.erase (pos); } arr.show (); }
数组应用 1.逆序字符串(使用双指针)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void Reverse (char arr[], int size) { char * p = arr; char * q = arr + size - 1 ; while (p < q) { char ch = *p; *p = *q; *q = ch; p++; q--; } } int main () { char arr[] = "hello world" ; Reverse (arr, strlen (arr)); std::cout << arr << std::endl; }
2.整型数组,把偶数调整到数组的左边,把奇数调整到数组的右边
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 void AdjustArray (int arr[], int size) { int * p = arr; int * q = arr + size - 1 ; while (p < q) { while (p < q) { if ((*p & 0x1 ) == 1 ) { break ; } p++; } while (p < q) { if ((*q & 0x1 ) == 0 ) { break ; } q--; } if (p < q) { int tmp = *p; *p = *q; *q = tmp; p++; q--; } } } int main () { int arr[10 ] = { 0 }; srand (time (0 )); for (int i = 0 ;i < 10 ;i++) { arr[i] = rand () % 100 ; } for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; AdjustArray (arr,10 ); for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; }
3.移除元素(leetcode 27)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution {public : int removeElement (vector<int >& nums, int val) { int i=0 ,j=nums.size ()-1 ; while (i<=j) { if (nums[i]!=val) { i++; continue ; } if (nums[j]==val) { j--; continue ; } nums[i++]=nums[j--]; } return i; } };
链表
特点:每一个节点都是在堆内存上独立出来的,节点内存不连续
优点:
内存利用率高,不需要大块连续内存
插入和删除节点不需要移动其他节点,时间复杂度O(1)
不需要专门进行扩容操作
缺点:
内存占用量大,每一个节点多出存放地址的空间
节点内存不连续,无法进行内存随机访问
链表搜索效率不高,只能从头节点开始逐节点遍历。
链表节点:
1 2 3 4 5 struct Node { int data; Node *next; }
单链表 每一个节点只能找到下一个节点,无法回退到上一个节点,末尾节点的指针域是nullptr
单链表接口实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 #include <iostream> #include <stdlib.h> #include <time.h> using namespace std;struct Node { Node (int data = 0 ) :data_ (data), next_ (nullptr ) {} int data_; Node* next_; }; class Clink { public : Clink () { head_ = new Node (); } ~Clink () { Node* p = head_; while (p != nullptr ) { head_ = head_->next_; delete p; p = head_; } head_ = nullptr ; } public : void insertTail (int val) { Node* p = head_; while (p->next_ != nullptr ) { p = p->next_; } Node* node = new Node (val); p->next_ = node; } void insertHead (int val) { Node* node = new Node (val); node->next_ = head_->next_; head_->next_ = node; } void Remove (int val) { Node* q = head_; Node* p = head_->next_; while (p != nullptr ) { if (p->data_ == val) { q->next_ = p->next_; delete p; return ; } else { q = p; p = p->next_; } } } void RemoveAll (int val) { Node* q = head_; Node* p = head_->next_; while (p != nullptr ) { if (p->data_ == val) { q->next_ = p->next_; delete p; p = q->next_; } else { q = p; p = p->next_; } } } bool Find (int val) { Node* p = head_->next_; while (p != nullptr ) { if (p->data_ == val) { return true ; } else { p = p->next_; } } return false ; } void Show () { Node* p = head_->next_; while (p != nullptr ) { std::cout << p->data_ << " " ; p = p->next_; } } private : Node* head_; }; int main () { Clink link; srand (time (0 )); for (int i = 0 ;i < 10 ;i++) { int val = rand () % 100 ; link.insertHead (val); std::cout << val << " " ; } std::cout << std::endl; link.Show (); std::cout << std::endl; link.insertHead (23 ); link.insertTail (23 ); link.Show (); std::cout << std::endl; link.RemoveAll (23 ); link.Show (); }
单链表应用 1.单链表逆序(leetcode206)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Solution {public : ListNode* reverseList (ListNode* head) { if (head==nullptr ) return nullptr ; ListNode prehead; prehead.next=head; ListNode *tmp=nullptr ; ListNode *cur=prehead.next; prehead.next=nullptr ; while (cur!=nullptr ) { tmp=cur->next; cur->next=prehead.next; prehead.next=cur; cur=tmp; } return prehead.next; } };
2.单链表求倒数第K个节点(leetcode 面试题02.02)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution {public : int kthToLast (ListNode* head, int k) { ListNode *pre=head; ListNode *p=head; for (int i=0 ;i<k;i++) { p=p->next; } while (p!=nullptr ) { p=p->next; pre=pre->next; } return pre->val; } };
3.合并两个有序单链表(leetcode 21)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class Solution {public : ListNode* mergeTwoLists (ListNode* list1, ListNode* list2) { ListNode prehead; prehead.next=list1; ListNode *last=&prehead; ListNode *p=list1; ListNode *q=list2; while (p!=nullptr && q!=nullptr ) { if (q->val<=p->val) { last->next=q; q=q->next; last=last->next; } else { last->next=p; p=p->next; last=last->next; } } if (p!=nullptr ) { last->next=p; } if (q!=nullptr ) { last->next=q; } return prehead.next; } };
4.单链表判断是否有环?求环的入口节点(leetcode 142)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Solution {public : ListNode *detectCycle (ListNode *head) { ListNode *fast=head; ListNode *slow=head; if (head==nullptr ||head->next==nullptr ) { return nullptr ; } while (fast!=nullptr &&fast->next!=nullptr ) { fast=fast->next->next; slow=slow->next; if (fast==slow) { ListNode *huan=head; while (huan!=fast) { huan=huan->next; fast=fast->next; } return huan; } } return nullptr ; } };
5.判断两个单链表是否相交(leetcode 160)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 class Solution {public : ListNode *getIntersectionNode (ListNode *headA, ListNode *headB) { ListNode *pa=headA; ListNode *pb=headB; int ia=0 ; int ib=0 ; while (pa!=nullptr ) { ia++; pa=pa->next; } while (pb!=nullptr ) { ib++; pb=pb->next; } pa=headA; pb=headB; if (ia>ib) { for (int i=ia-ib;i>0 ;i--) pa=pa->next; while (pa!=pb) { pa=pa->next; pb=pb->next; } if (pa==nullptr ) { return nullptr ; } return pa; }else { for (int i=ib-ia;i>0 ;i--) pb=pb->next; while (pa!=pb) { pa=pa->next; pb=pb->next; } if (pa==nullptr ) { return nullptr ; } return pa; } } };
6.删除链表倒数第N个节点(leetcode 19)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Solution {public : ListNode* removeNthFromEnd (ListNode* head, int n) { if (n<=0 ) return nullptr ; ListNode *p=head; ListNode *q=head; for (int i=0 ;i<n;i++) { q=q->next; } if (q==nullptr ) { head=head->next; return head; } while (q->next!=nullptr ) { p=p->next; q=q->next; } p->next=p->next->next; return head; } };
7.旋转链表(leetcode 61)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Solution {public : ListNode* rotateRight (ListNode* head, int k) { if (head==nullptr ) { return nullptr ; } ListNode *p=head; int i=0 ; while (p!=nullptr ) { i++; p=p->next; } p=head; ListNode *q=head; for (int j=0 ;j<(k%i);j++) { q=q->next; } while (q->next!=nullptr ) { p=p->next; q=q->next; } q->next=head; head=p->next; p->next=nullptr ; return head; } };
单向循环链表 特点
每一个节点除了数据域,还有一个next指针域指向下一个节点。
末尾节点的指针域指向头节点。
接口实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 #include <iostream> #include <stdlib.h> #include <time.h> class CircleLink { public : CircleLink () { head_ = new Node (); tail_ = head_; head_->next_ = head_; } ~CircleLink () { Node* p = head_->next_; while (p != head_) { head_->next_ = p->next_; delete p; p = head_->next_; } delete head_; } void InsertTail (int val) { Node* p = new Node (val); p->next_ = head_; tail_->next_ = p; tail_ = p; } void InsertHead (int val) { Node* node = new Node (val); node->next_ = head_->next_; head_->next_ = node; if (node->next_ == head_) { tail_ = node; } } void Remove (int val) { Node* q = head_; Node* p = head_->next_; while (p!= head_) { if (p->data_ == val) { q->next_ = p->next_; delete p; if (q->next_ == head_) { tail_ = q; } return ; } else { q = p; p = p->next_; } } } bool Find (int val) const { Node* p = head_->next_; while (p != head_) { if (p->data_ == val) { return true ; }else { p = p->next_; } } return false ; } void Show () const { Node* p = head_->next_; while (p != head_) { std::cout << p->data_ << " " ; p = p->next_; } std::cout << std::endl; } private : struct Node { Node (int data = 0 ) :data_ (data), next_ (nullptr ) {} int data_; Node* next_; }; Node* head_; Node* tail_; }; int main () { CircleLink clink; srand (time (NULL )); clink.InsertHead (100 ); for (int i = 0 ;i < 10 ;i++) { clink.InsertTail (rand () % 100 ); } clink.Show (); clink.InsertTail (200 ); clink.Show (); clink.Remove (200 ); clink.Show (); }
约瑟夫环问题
已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌,从编号为k的人开始报数,数到m的那个人出列,他的下一个人又从1开始报数,数到m的那个人又出列,依此规律重复下去,直到全部出列,求输出人的顺序?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 #include <iostream> #include <stdlib.h> #include <time.h> struct Node { Node (int data = 0 ) :data_ (data), next_ (nullptr ) {} int data_; Node* next_; }; void Joseph (Node* head,int k,int m) { Node* p = head; Node* q = head; while (q->next_ != head) { q = q->next_; } for (int i = 1 ;i < k;i++) { q = p; p = p->next_; } for (;;) { for (int i = 1 ;i < m;i++) { q = p; p = p->next_; } std::cout << p->data_ << " " ; if (p == q) { delete p; break ; } q->next_ = p->next_; delete p; p = q->next_; } } int main () { Node* head = new Node (1 ); Node* n2 = new Node (2 ); Node* n3 = new Node (3 ); Node* n4 = new Node (4 ); Node* n5 = new Node (5 ); Node* n6 = new Node (6 ); Node* n7 = new Node (7 ); Node* n8 = new Node (8 ); head->next_ = n2; n2->next_ = n3; n3->next_ = n4; n4->next_ = n5; n5->next_ = n6; n6->next_ = n7; n7->next_ = n8; n8->next_ = head; Joseph (head, 1 , 3 ); }
双向链表 特点:
每一个节点除了数据域,还有next指针域指向下一个节点,pre指针域指向前一个节点
头节点的pre是NULL,末尾节点的next是NULL。
接口实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 #include <iostream> struct Node { Node (int data = 0 ) : data_ (data) , next_ (nullptr ) , pre_ (nullptr ) {} int data_; Node* next_; Node* pre_; }; class DoubleLink { public : DoubleLink () { head_ = new Node (); } ~DoubleLink () { Node* p = head_; while (p != nullptr ) { Node* q = p->next_; delete p; p = q; } } public : void InsertHead (int val) { Node* node = new Node (val); node->next_ = head_->next_; node->pre_ = head_; if (head_->next_ != nullptr ) { head_->next_->pre_ = node; } head_->next_ = node; } void InsertTail (int val) { Node* p = head_; while (p->next_ != nullptr ) { p = p->next_; } Node* node = new Node (val); p->next_ = node; node->pre_ = p; } void Remove (int val) { Node* p = head_->next_; while (p != nullptr ) { if (p->data_ == val) { p->pre_->next_ = p->next_; if (p->next_ != nullptr ) { p->next_->pre_ = p->pre_; } Node* next = p->next_; delete p; p = next; } else { p = p->next_; } } } bool Find (int val) { Node* p = head_->next_; while (p != nullptr ) { if (p->data_ == val) { return true ; } p = p->next_; } return false ; } void Show () { Node* p = head_->next_; while (p != nullptr ) { std::cout << p->data_ << " " ; p = p->next_; } std::cout << std::endl; } private : Node* head_; }; int main () { DoubleLink dLink; dLink.InsertTail (20 ); dLink.InsertTail (12 ); dLink.InsertTail (78 ); dLink.InsertTail (32 ); dLink.InsertTail (7 ); dLink.InsertTail (90 ); dLink.Show (); dLink.InsertHead (100 ); dLink.Show (); dLink.Remove (78 ); dLink.Show (); }
双向循环链表 特点:
每一个节点除了数据域,还有next指针域指向下一个节点,pre指针域指向前一个节点。
头节点的pre指向末尾节点,末尾节点的next指向头节点。
C++ list容器底层就是双向循环链表
接口实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 118 119 120 121 122 123 124 125 126 127 128 #include <iostream> struct Node { Node (int data = 0 ) : data_ (data) , next_ (nullptr ) , pre_ (nullptr ) {} int data_; Node* next_; Node* pre_; }; class DoubleCircleLink { public : DoubleCircleLink () { head_ = new Node (); head_->next_ = head_; head_->pre_ = head_; } ~DoubleCircleLink () { Node* p = head_->next_; while (p != head_) { Node* q = p->next_; delete p; p = q; } delete head_; head_ = nullptr ; } public : void InsertHead (int val) { Node* node = new Node (val); node->next_ = head_->next_; node->pre_ = head_; head_->next_->pre_ = node; head_->next_ = node; } void InsertTail (int val) { Node* p = head_->pre_; Node* node = new Node (val); node->next_ = head_; node->pre_ = p; head_->pre_ = node; p->next_ = node; } void Remove (int val) { Node* p = head_->next_; while (p != head_) { if (p->data_ == val) { p->pre_->next_ = p->next_; p->next_->pre_ = p->pre_; Node* next = p->next_; delete p; p = next; } else { p = p->next_; } } } bool Find (int val) { Node* p = head_->next_; while (p != head_) { if (p->data_ == val) { return true ; } p = p->next_; } return false ; } void Show () { Node* p = head_->next_; while (p != head_) { std::cout << p->data_ << " " ; p = p->next_; } std::cout << std::endl; } private : Node* head_; }; int main () { DoubleCircleLink dLink; dLink.InsertTail (20 ); dLink.InsertTail (12 ); dLink.InsertTail (78 ); dLink.InsertTail (32 ); dLink.InsertTail (7 ); dLink.InsertTail (90 ); dLink.Show (); dLink.InsertHead (100 ); dLink.Show (); dLink.Remove (78 ); dLink.Show (); }
栈 特点: 先进后出,后进先出
顺序栈 依赖数组实现
接口实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 #include <iostream> class SeqStack { public : SeqStack (int size = 10 ) :mtop (0 ) ,mcap (size) { mpStack = new int [mcap]; } ~SeqStack () { delete [] mpStack; mpStack = nullptr ; } public : void push (int val) { if (mtop == mcap) { expand (2 * mcap); } mpStack[mtop++] = val; } void pop () { if (mtop == 0 ) { throw "stack is empty!" ; } mtop--; } int top () const { if (mtop == 0 ) { throw "stack is empty!" ; } return mpStack[mtop - 1 ]; } bool empty () const { return mtop == 0 ; } int size () const { return mtop; } private : void expand (int size) { int * p = new int [size]; memcpy (p, mpStack, mtop*sizeof (int )); delete [] mpStack; mpStack = p; mcap = size; } private : int * mpStack; int mtop; int mcap; }; int main () { int arr[] = { 12 ,4 ,56 ,7 ,89 ,31 ,53 ,75 }; SeqStack s; for (int v : arr) { s.push (v); } while (!s.empty ()) { std::cout << s.top () << " " ; s.pop (); } std::cout<<std::endl; }
链式栈 依赖链表实现
接口实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 #include <iostream> class LinkStack { public : LinkStack ():size_ (0 ) { head_ = new Node (); } ~LinkStack () { Node* p = head_->next_; while (p != nullptr ) { head_->next_ = p->next_; delete p; p = head_->next_; } delete head_; head_ = nullptr ; } public : void push (int val) { Node* node = new Node (val); node->next_ = head_->next_; head_->next_ = node; size_++; } void pop () { if (head_->next_ == nullptr ) { throw "Stack is empty!" ; } Node* p = head_->next_; head_->next_ = p->next_; delete p; size_--; } int top () const { if (head_->next_ == nullptr ) { throw "Stack is empty!" ; } return head_->next_->data_; } bool empty () const { return head_->next_ == nullptr ; } int size () const { return size_; } private : struct Node { Node (int data=0 ) :data_ (data) , next_ (nullptr ) {} int data_; Node* next_; }; int size_; Node* head_; }; int main () { int arr[] = { 12 ,4 ,56 ,7 ,89 ,31 ,53 ,75 }; LinkStack s; for (int v : arr) { s.push (v); } while (!s.empty ()) { std::cout << s.top () << " " ; s.pop (); } std::cout << std::endl; }
栈的应用 1.有效的括号(leetcode 20)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution {public : bool isValid (string s) { std::stack<char > cs; for (char ch:s) { if (ch=='(' ||ch=='[' ||ch=='{' ) { cs.push (ch); }else { if (cs.empty ()) { return false ; } char cmp=cs.top (); cs.pop (); if (ch==')' &&cmp!='(' || ch==']' &&cmp!='[' ||ch=='}' &&cmp!='{' ) { return false ; } } } if (!cs.empty ()) return false ; return true ; } };
2.逆波兰表达式(后缀表达式)求值(leetcode 150)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Solution {public : int calc (int left,int right,char sign) { switch (sign) { case '+' : return left+right; case '-' : return left-right; case '*' : return left*right; case '/' : return left/right; } throw "" ; } int evalRPN (vector<string>& tokens) { std::stack<int > intStack; for (string &str:tokens) { if (str.size ()==1 && (str[0 ]=='+' ||str[0 ]=='-' ||str[0 ]=='*' ||str[0 ]=='/' )) { int right=intStack.top (); intStack.pop (); int left=intStack.top (); intStack.pop (); intStack.push (calc (left,right,str[0 ])); }else { intStack.push (stoi (str)); } } return intStack.top (); } };
3.中缀转后缀表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 #include <iostream> #include <string> #include <stack> bool Priority (char ch, char topch) { if ((ch == '*' || ch == '/' ) && (topch == '+' || topch == '-' )) { return true ; } if (topch == '(' &&ch!=')' ) { return true ; } return false ; } std::string MiddleToEndExpr (std::string expr) { std::string result; std::stack < char > s; for (char ch : expr) { if (ch >= '0' && ch <= '9' ) { result += ch; } else { for (;;) { if (s.empty () || ch == '(' ) { s.push (ch); break ; } char topch = s.top (); if (Priority (ch, topch)) { s.push (ch); break ; } else { s.pop (); if (topch == '(' ) break ; result += topch; } } } } while (!s.empty ()) { result += s.top (); s.pop (); } return result; } int main () { std::cout << MiddleToEndExpr ("(1+2)*(3+4)" ) << std::endl; std::cout << MiddleToEndExpr ("2+(4+6)/2+6/3" ) << std::endl; std::cout << MiddleToEndExpr ("2+6/(4-2)+(4+6)/2" ) << std::endl; }
队列 特点:先进先出,后进后出
环形队列
依赖数组实现,但必须实现环形
接口实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 #include <iostream> class Queue { public : Queue (int size = 10 ) :cap_ (size) , front_ (0 ) , rear_ (0 ) , size_ (0 ) { pQue_ = new int [cap_]; } ~Queue () { delete [] pQue_; pQue_ = nullptr ; } public : void push (int val) { if ((rear_ + 1 ) % cap_ == front_) { expand (2 * cap_); } pQue_[rear_] = val; size_++; rear_ = (rear_ + 1 ) % cap_; } void pop () { if (front_ == rear_) throw "queue is empty!" ; front_ = (front_ + 1 ) % cap_; size_--; } int front () const { if (front_ == rear_) throw "queue is empty!" ; return pQue_[front_]; } int back () const { if (front_ == rear_) throw "queue is empty!" ; return pQue_[(rear_ - 1 + cap_) % cap_]; } bool empty () const { return front_ == rear_; } int size () const { return size_; } private : void expand (int size) { int * p = new int [size]; int i = 0 ; int j = front_; for (;j!=rear_;i++,j=(j+1 )%cap_) { p[i] = pQue_[j]; } cap_ = size; delete [] pQue_; pQue_ = p; front_ = 0 ; rear_ = i; } private : int * pQue_; int cap_; int front_; int rear_; int size_; }; int main () { int arr[] = { 12 ,4 ,56 ,7 ,89 ,31 ,53 ,75 }; Queue que; for (int v : arr) { que.push (v); } std::cout << que.front () << std::endl; std::cout << que.back () << std::endl; que.push (100 ); que.push (200 ); que.push (300 ); std::cout << que.front () << std::endl; std::cout << que.back () << std::endl; while (!que.empty ()) { std::cout << que.front () << " " << que.back () << std::endl;; que.pop (); } }
链式队列 依赖链表实现,通过双向循环链表实现
接口实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 #include <iostream> class LinkQueue { public : LinkQueue () { head_ = new Node (); head_->next_ = head_; head_->pre_ = head_; } ~LinkQueue () { Node* p = head_->next_; while (p != head_) { head_->next_ = p->next_; p->next_->pre_ = head_; delete p; p = head_->next_; } delete head_; head_ = nullptr ; } public : void push (int val) { Node* node = new Node (val); node->next_ = head_; node->pre_ = head_->pre_; head_->pre_->next_ = node; head_->pre_ = node; } void pop () { if (head_->next_ == head_) throw "队列为空" ; Node* p = head_->next_; head_->next_ = p->next_; p->next_->pre_ = head_; delete p; } int front () const { if (head_->next_ == head_) throw "队列为空" ; return head_->next_->data_; } int back () const { if (head_->next_ == head_) throw "队列为空" ; return head_->pre_->data_; } bool empty () { return head_->next_ == head_; } private : struct Node { Node (int data=0 ) :data_ (data) ,next_ (nullptr ) ,pre_ (nullptr ) {} int data_; Node* next_; Node* pre_; }; Node* head_; }; int main () { int arr[] = { 12 ,4 ,56 ,7 ,89 ,31 ,53 ,75 }; LinkQueue que; for (int v : arr) { que.push (v); } std::cout << que.front () << std::endl; std::cout << que.back () << std::endl; que.push (100 ); que.push (200 ); que.push (300 ); std::cout << que.front () << std::endl; std::cout << que.back () << std::endl; while (!que.empty ()) { std::cout << que.front () << " " << que.back () << std::endl;; que.pop (); } }
队列应用 1.用栈实现队列(leetcode 232)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 class MyQueue {public : MyQueue () { } void push (int x) { s1. push (x); } int pop () { if (s1. empty ()&&s2. empty ()) { throw "队列为空" ; }else if (s2. empty ()) { while (!s1. empty ()) { int x=s1. top (); s2. push (x); s1. pop (); } } int x=s2. top (); s2. pop (); return x; } int peek () { if (s1. empty ()&&s2. empty ()) { throw "队列为空" ; }else if (s2. empty ()) { while (!s1. empty ()) { int x=s1. top (); s2. push (x); s1. pop (); } } return s2. top (); } bool empty () { return s1. empty ()&&s2. empty (); } private : std::stack<int > s1,s2; };
2.用队列实现栈(leetcode 225)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 class MyStack {public : MyStack () { } void push (int x) { que.push (x); if (!que.empty ()) { for (int i=1 ;i<que.size ();i++) { int val=que.front (); que.pop (); que.push (val); } } } int pop () { if (que.empty ()) { throw "栈为空" ; } int val=que.front (); que.pop (); return val; } int top () { if (que.empty ()) { throw "栈为空" ; } return que.front (); } bool empty () { return que.empty (); } private : std::queue<int > que; };
搜索 二分搜索 代码实现和复杂度分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <iostream> int BinarySearch (int arr[], int size, int val) { int first = 0 ; int last = size - 1 ; while (first <= last) { int mid = (first + last) / 2 ; if (arr[mid] == val) { return mid; } else if (arr[mid] > val) { last = mid - 1 ; } else { first = mid + 1 ; } } return -1 ; } int main () { int arr[] = { 12 ,25 ,34 ,39 ,45 ,57 ,63 ,78 ,82 ,96 ,100 }; int size = sizeof (arr) / sizeof (arr[0 ]); std::cout << BinarySearch (arr, size, 39 ) << std::endl; std::cout << BinarySearch (arr, size, 45 ) << std::endl; std::cout << BinarySearch (arr, size, 12 ) << std::endl; std::cout << BinarySearch (arr, size, 64 ) << std::endl; }
递归实现
递归代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 int BinarySearch1 (int arr[], int i, int j, int val) { if (i > j) { return -1 ; } int mid = (i + j) / 2 ; if (arr[mid] == val) { return mid; } else if (arr[mid] > val) { BinarySearch1 (arr, i, mid - 1 , val); } else { BinarySearch1 (arr, mid + 1 , j, val); } } int BinarySearch (int arr[], int size, int val) { return BinarySearch1 (arr, 0 , size-1 , val); } int main () { int arr[] = { 12 ,25 ,34 ,39 ,45 ,57 ,63 ,78 ,82 ,96 ,100 }; int size = sizeof (arr) / sizeof (arr[0 ]); std::cout << BinarySearch (arr, size, 39 ) << std::endl; std::cout << BinarySearch (arr, size, 45 ) << std::endl; std::cout << BinarySearch (arr, size, 12 ) << std::endl; std::cout << BinarySearch (arr, size, 64 ) << std::endl; }
排序算法 冒泡排序
特点:相邻元素两两比较,把值大的元素往下交换。
缺点:数据交换次数太多了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <iostream> #include <stdlib.h> #include <time.h> void BubbleSort (int arr[], int size) { for (int i = 0 ;i < size - 1 ;i++) { bool flag = false ; for (int j = 0 ;j < size - 1 - i;j++) { if (arr[j] > arr[j + 1 ]) { int tmp = arr[j]; arr[j] = arr[j + 1 ]; arr[j + 1 ] = tmp; flag = true ; } } if (!flag) return ; } } int main () { int arr[10 ]; srand (time (NULL )); for (int i = 0 ;i < 10 ;i++) { arr[i] = rand () % 100 + 1 ; } for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; BubbleSort (arr, 10 ); for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; }
选择排序 特点:每次在剩下的元素中选择值最小的元素,和当前元素进行交换。
缺点:相比于冒泡排序,交换的次数少了,但是比较的次数依然很多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <iostream> #include <stdlib.h> #include <time.h> void ChoiceSort (int arr[], int size) { for (int i = 0 ;i < size - 1 ;i++) { int min = arr[i]; int k = i; for (int j = i+1 ;j < size;j++) { if (min > arr[j]) { min = arr[j]; k = j; } } if (i != k) { int tmp = arr[i]; arr[i] = arr[k]; arr[k] = tmp; } } } int main () { int arr[10 ]; srand (time (NULL )); for (int i = 0 ;i < 10 ;i++) { arr[i] = rand () % 100 + 1 ; } for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; ChoiceSort (arr, 10 ); for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; }
插入排序 特点:从第二个元素开始,把前面的元素序列当作已经有序的,然后找合适的位置插入。
优点:插入排序是普通排序中效率最高的排序算法,而且在数据越趋于有序的情况下,插入排序的效率是最高的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <iostream> #include <stdlib.h> #include <time.h> void InsertSort (int arr[], int size) { for (int i = 1 ;i < size;i++) { int val = arr[i]; int j = i - 1 ; for (;j >= 0 ;j--) { if (arr[j] <= val) { break ; } arr[j + 1 ] = arr[j]; } arr[j + 1 ] = val; } } int main () { int arr[10 ]; srand (time (NULL )); for (int i = 0 ;i < 10 ;i++) { arr[i] = rand () % 100 + 1 ; } for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; InsertSort (arr, 10 ); for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; }
希尔排序 特点:可以看作是多路的插入排序,分组的数据越趋于有序,整体的数据也越趋于有序,插入排序效率完美体现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <iostream> #include <stdlib.h> #include <time.h> void ShellSort (int arr[], int size) { for (int gap = size / 2 ;gap != 0 ;gap = gap / 2 ) { for (int i = gap;i < size;i++) { int val = arr[i]; int j = i - gap; for (;j >= 0 ;j-=gap) { if (arr[j] <= val) { break ; } arr[j + gap] = arr[j]; } arr[j + gap] = val; } } } int main () { int arr[10 ]; srand (time (NULL )); for (int i = 0 ;i < 10 ;i++) { arr[i] = rand () % 100 + 1 ; } for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; ShellSort (arr, 10 ); for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; }
四种排序排序性能对比:
排序算法
100000组(单位:ms)
冒泡排序(效率最低)
19.32s
选择排序(效率次之)
4.066s
插入排序(效率最高)
3.475s
希尔排序(效率更高)
0.019s
排序算法
平均时间复杂度
最好时间复杂度
最坏时间复杂度
空间复杂度
稳定性
冒泡排序
O(n^2)
O(n)
O(n^2)
O(1)
稳定
选择排序
O(n^2)
O(n^2)
O(n^2)
O(1)
不稳定
插入排序
O(n^2)
O(n)
O(n^2)
O(1)
稳定
希尔排序
依赖不同的增量序列设置O(n^1.3)
O(n)
O(n^2)
O(1)
不稳定
插入排序的效率最好,尤其在数据已经趋于有序的情况下,采用插入排序效率最高 。
一般中等数据量的排序都用希尔排序,选择合适的增量序列,效率就已经不错了,如果数据量比较大,可以选择高级的排序算法,如快速排序。
快速排序 冒泡排序的升级算法。
每次选择基准数,把小于基准数的放到基准数的左边,把大于基准数的放到基准数的右边,采用“分治思想”处理剩余的序列元素,直到整个序列变为有序序列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 #include <iostream> #include <stdlib.h> #include <time.h> int Partation (int arr[], int l, int r) { int val = arr[l]; while (l < r) { while (l<r && arr[r] > val) { r--; } if (l < r) { arr[l] = arr[r]; l++; } while (l<r && arr[l]<val) { l++; } if (l < r) { arr[r] = arr[l]; r--; } } arr[l] = val; return l; } void QuickSort (int arr[], int begin, int end) { if (begin >= end) { return ; } int pos = Partation (arr, begin, end); QuickSort (arr, begin, pos - 1 ); QuickSort (arr, pos + 1 , end); } void QuickSort (int arr[], int size) { return QuickSort (arr, 0 , size - 1 ); } int main () { int arr[10 ]; srand (time (NULL )); for (int i = 0 ;i < 10 ;i++) { arr[i] = rand () % 100 + 1 ; } for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; QuickSort (arr, 10 ); for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; }
算法效率提升:
对于小段趋于有序的序列采用插入排序。(设定序列小到一定数量使用插入排序)
三数取中法。取序列中间的数arr[(l+r)/2],旨在挑选合适的基准数,防止快排退化成冒泡排序
随机数法
归并排序 也采用“分治思想”,先进行序列划分,再进行元素的有序合并。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 #include <iostream> #include <stdlib.h> #include <time.h> void Merge (int arr[], int l, int m, int r, int * p) { int idx = 0 ; int i = l; int j = m + 1 ; while (i <= m && j <= r) { if (arr[i] <= arr[j]) { p[idx++] = arr[i++]; } else { p[idx++] = arr[j++]; } } while (i <= m) { p[idx++] = arr[i++]; } while (j <= r) { p[idx++] = arr[j++]; } for (i = l, j = 0 ;i <= r;i++, j++) { arr[i] = p[j]; } p = nullptr ; } void MergeSort (int arr[], int begin, int end, int * p) { if (begin >= end) return ; int mid = (begin + end) / 2 ; MergeSort (arr, begin, mid, p); MergeSort (arr, mid + 1 , end, p); Merge (arr, begin, mid, end, p); } void MergeSort (int arr[], int size) { int * p = new int [size]; MergeSort (arr, 0 , size - 1 , p); delete []p; } int main () { int arr[10 ]; srand (time (NULL )); for (int i = 0 ;i < 10 ;i++) { arr[i] = rand () % 100 + 1 ; } for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; MergeSort (arr, 10 ); for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; }
堆排序 二叉堆
就是一颗完全二叉树 ,分为两种典型的堆,分别是大根堆 和小根堆
满足0<=i<=(n-1)/2,n代表最后一个元素的下标。
如果arr[i]<=arr[2i+1]&&arr[i]<=arr[2 i+1+2],就是小根堆
如果arr[i]>=arr[2i+1]&&arr[i]>=arr[2 i+1+2],就是大根堆
基于堆的优先级队列代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 #include <iostream> #include <functional> #include <stdlib.h> #include <time.h> class PriorityQueue { public : using Comp = std::function<bool (int , int )>; PriorityQueue (int cap=20 , Comp comp = std::greater <int >()) :size_ (0 ) , cap_ (cap) , comp_ (comp) { que_ = new int [cap_]; } PriorityQueue (Comp comp) :size_ (0 ) , cap_ (20 ) , comp_ (comp) { que_ = new int [cap_]; } ~PriorityQueue () { delete [] que_; que_ = nullptr ; } public : void push (int val) { if (size_ == cap_) { int * p = new int [2 * cap_]; memcpy (p, que_, cap_ * sizeof (int )); delete [] que_; que_ = p; cap_ *= 2 ; } if (size_ == 0 ) { que_[size_] = val; } else { shiftUp (size_,val); } size_++; } void pop () { if (size_ == 0 ) { throw "container is empty!" ; } size_--; if (size_ > 0 ) { shiftDown (0 ,que_[size_]); } } bool empty () const { return size_ == 0 ; } int top () const { if (size_ == 0 ) { throw "container is empty!" ; } return que_[0 ]; } int size () const { return size_; } private : void shiftUp (int i, int val) { while (i > 0 ) { int father = (i - 1 ) / 2 ; if (comp_ (val, que_[father])) { que_[i] = que_[father]; i = father; } else { break ; } } que_[i] = val; } void shiftDown (int i, int val) { while (2 * i + 1 < size_) { int child = 2 * i + 1 ; if (child + 1 < size_ && comp_ (que_[child + 1 ], que_[child])) { child = child + 1 ; } if (comp_ (que_[child], val)) { que_[i] = que_[child]; i = child; } else { break ; } } que_[i] = val; } private : int * que_; int size_; int cap_; Comp comp_; }; int main () { PriorityQueue que ([](int a, int b) {return a < b;}) ; srand (time (NULL )); for (int i = 0 ;i < 10 ;i++) { que.push (rand () % 100 ); } while (!que.empty ()) { std::cout << que.top () << " " ; que.pop (); } std::cout << std::endl; }
堆排序代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 #include <iostream> #include <stdlib.h> #include <time.h> void shiftDown (int arr[], int i, int size) { int val = arr[i]; while (2 * i + 1 < size) { int child = 2 * i + 1 ; if (child + 1 < size && arr[child + 1 ] > arr[child]) { child = child + 1 ; } if (arr[child] > val) { arr[i] = arr[child]; i = child; } else { break ; } } arr[i] = val; } void HeapSort (int arr[], int size) { int n = size - 1 ; for (int i = (n - 1 ) / 2 ;i >= 0 ;i--) { shiftDown (arr, i, size); } for (int i = n;i > 0 ;i--) { int tmp = arr[i]; arr[i] = arr[0 ]; arr[0 ] = tmp; shiftDown (arr, 0 , i); } } int main () { int arr[10 ]; srand (time (NULL )); for (int i = 0 ;i < 10 ;i++) { arr[i] = rand () % 100 + 1 ; } for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; HeapSort (arr, 10 ); for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; }
排序算法
平均时间复杂度
最好时间复杂度
最坏时间复杂度
空间复杂度
稳定性
堆排序
O(n*logn)
O(n*logn)
O(n*logn)
O(1)
不稳定
快速排序
O(n*logn)
O(n*logn)
O(n^2)
O(logn)->O(n)
不稳定
归并排序
O(n*logn)
O(n*logn)
O(n*logn)
O(n)
稳定
快排&归并&堆排序&希尔性能测试
数据规模
快速排序
归并排序
希尔排序
堆排序
1000万
0.974s
1.605s
3.257s
2.274s
5000万
4.871s
8.471s
18.712s
14.718s
1亿
9.717s
17.266s
41.887s
32.279s
1.不管是快排,或者归并排序,遍历元素的时候都是按照顺序遍历的,对CPU缓存是友好的(CPU缓存命中率高)。但是堆排序,访问元素的时候,是按照父子节点的关系访问的,并不是按照顺序访问的,所以排序过程中,不管是进行元素上浮调整,还是下沉调整,对CPU缓存不友好
2.堆排序的过程中,进行元素下沉调整所作的无效比较过多(因为它本身就小,所以最终下沉到的地方,和末尾位置相差不远,中间做了很多比较,无用功太多)
高级算法的一些问题 1.STL里sort算法用的是什么排序算法?
首先用的是快速排序算法,如果待排序的序列个数<=32变为插入排序,如果递归层数太深转为堆排序。
2.快速排序的时间复杂度不是稳定的nlogn,最坏情况会变成n^2,怎么解决复杂度恶化的问题?
三数取中(选择合理的基准数)
3.快速排序递归实现时,怎么解决递归层次过深的问题?
可以设计一个ideal,当ideal达到一个值后使用堆排。(stl::sort用法)
4.递归过深会引发什么问题?
函数开销变大导致栈内存溢出,程序挂掉。
5.怎么控制递归深度?如果达到递归深度了还没排完序怎么办?
达到递归深度可以使用非递归排序算法。
6.假设你只有100Mb的内存,需要对1Gb的数据进行排序,最合适的算法是归并算法
基数排序
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include <iostream> #include <stdlib.h> #include <time.h> #include <string> #include <vector> void RadixSort (int arr[], int size) { int maxData = arr[0 ]; for (int i = 1 ;i < size;i++) { if (maxData < abs (arr[i])) { maxData = abs (arr[i]); } } int len = std::to_string (maxData).size (); std::vector<std::vector<int >> vecs; int mod = 10 ; int dev = 1 ; for (int i = 0 ;i < len;i++, mod *= 10 , dev *= 10 ) { vecs.resize (20 ); for (int j = 0 ;j < size;j++) { int index = arr[j] % mod / dev+10 ; vecs[index].push_back (arr[j]); } int idx = 0 ; for (auto vec : vecs) { for (int v : vec) { arr[idx++] = v; } } vecs.clear (); } } int main () { int arr[10 ]; srand (time (NULL )); for (int i = 0 ;i < 10 ;i++) { arr[i] = rand () % 100 + 1 ; } for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; RadixSort (arr, 10 ); for (int v : arr) { std::cout << v << " " ; } std::cout << std::endl; }
排序算法
平均时间复杂度
最好时间复杂度
最坏时间复杂度
空间复杂度
稳定性
基数排序
O(nd)
O(nd)
O(nd)
O(n)
稳定
哈希表
线性探测哈希表实现
代码实现: