数组 优点
下标访问的复杂度为O(1)
末尾位置增加删除元素时间复杂度为O(1) 删除就是–,增加就是++
缺点
非末尾元素增加位置需要大量的数据移动
搜索的复杂度是O(n) 注意搜索和访问不是一个东西,搜索是遍历寻找这个值,而访问是a[0]–a[n-1],在无序数组 中采用遍历,是线性搜索 ;在有序数组 中采用二分,复杂度为O(logn)
链表 优点
内存利用率高,不需要大块连续内存
插入和删除节点不需要移动其它节点,时间复杂度O(1)
不需要专门进行扩容操作
缺点
内存占用量大,每一个节点多出存放地址的空间
节点内存不连续,无法进行内存随机访问
链表搜索效率不高,只能从头节点开始逐节点遍历
内存碎片化 现在我们有100M的内存空间(堆上),内存的释放是一块一块的,谁用完了谁就被释放了 ,如果将中间的20M和最右边的10M手动释放掉;我们就得到了两块内存碎片;现在我们有30M的空闲空间。 如果现在进程运行需要25M的内存空间,我们能不能分配25M的数组呢? 不能 ,数组的内存是绝对连续的 在内存碎片 过多的情况下,无法开辟大数组,这时候就可以用链表 ,每一个节点都是独立new出来的 从头节点访问到最后一个节点,最后一个节点的地址域为NULL 。
1 2 3 4 5 struct Node { Node (int data = 0 ) : data_ (data),Next_ (nullptr ){} int data_; Node *Next_; };
结构体其实就是一个public的类,现在写了一个Node 的构造函数,如果不传参默认data就是0。为什么后面要加一个大括号?因为前面在初始化,初始化已经完成了,后面是函数体{}可以是空的。 不要总去判断当前节点的下一节点是否为空 ,老老实实判断当前节点是否为空,除非你真的要去找末尾节点。
链表接口 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 #include <iostream> #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; } } 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 *p = head->next_; Node *q = p; 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 *p = head->next_; Node *q = head; while (p!=nullptr ) { if (p->data_==val) { q->next_ = p->next_; delete p; p = q->next_; } else { q = p; p = p->next_; } } } void show () { Node *p = head->next_; while (p!=nullptr ) { cout<<p->data_<<" " ; p = p->next_; } cout<<endl; } bool Find (int val) { Node *p = head->next_; while (p!=nullptr ) { if (p->data_==val) { return true ; } else { p = p->next_; } } } private : Node *head; }; int main () { Clink c; srand (time (0 )); for (int i = 0 ;i<10 ;i++) { int val = rand ()%100 ; c.InsertHead (val); cout<<val<<" " ; } cout<<endl; c.InsertHead (23 ); c.InsertHead (23 ); c.InsertTail (23 ); c.show (); c.RemoveAll (23 ); c.show (); }
总结:
数组:下标访问/随机访问多、搜索
链表:增加、删除多 不过还是要看插入哪里,如果插入中间位置都是O(n)
后续我自己rewrite发现语法出问题
1 2 3 4 5 6 7 8 public : mylink (){ Node head () ; } ~mylink (){ }
我一开始写的是没有注释的 Node head() 这样程序无法运行,为什么呢?因为这是一个函数声明 ,返回值为Node的函数声明!并没有创造出对象!所以我们需要new一个对象出来! 而且不要 空想一个头节点 ,题目如果给你链表是:1,2,3,4,5。那就是这样,第一个节点就是头节点。
单链表逆序 头插法
单链表求倒数第k个节点 双指针,第一个指针先移动k
合并两个有序的单链表 双指针比较大小
栈 先进后出
栈接口 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 #include <iostream> #include <time.h> using namespace std; 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 (); } 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 () { return mtop==0 ; } int size () { return mtop; } private : void expand () { int *p = new int [mcap*2 ]; for (int i = 0 ;i<mcap;i++) { p[i] = mpStack[i]; } delete []mpStack; mpStack = p; mcap = 2 *mcap; } private : int *mpStack; int mtop; int mcap; }; int main () { srand (time (0 )); SeqStack stack (5 ) ; for (int i = 0 ;i<5 ;i++) { stack.push (rand ()%10 ); } stack.push (10 ); while (!stack.empty ()) { cout<<stack.top ()<<" " ; stack.pop (); } }
队列 环形队列 不能用++,留下一个位置来判断是否为空
入队:(rear+1)%length
出队(first+1)%length
满 (rear+1)%length =first
空rear = rear
排序算法
冒泡排序:相邻两两元素比较,值大/小的元素往下交换;缺点:数据交换次数太多
选择排序:每次在剩下的数据选择最大/最小的数据和当前元素进行交换;缺点:交换次数仍然多
插入排序:**如果数据趋于有序,那么插入排序是所有排序算法中效率最高的算法!**插入排序效率>冒泡&&选择,不仅没有交换,而且比较的次数也少。
希尔排序:插入排序PLUS,从全局先将数据调整为趋于有序。对数据进行分组插入排序
快速排序:选取一个基准数 ,把小于基准数的元素放到基准数左边,大于基准数的元素放在右边;然后对基准数两边的序列进行同样的操作 (递归)
归并排序: 二路归并
冒泡排序 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> #include <time.h> #include <vector> using namespace std;void bubble (vector<int >&arr) { for (int i = 0 ;i<arr.size ();i++) { for (int j = 0 ;j<arr.size ()-i-1 ;j++) { if (arr[j]>arr[j+1 ]) { swap (arr[j],arr[j+1 ]); } } } } void show (vector<int > &arr) { for (auto a: arr) { cout<<a<<" " ; } } int main () { srand (time (0 )); vector<int > arr; for (int i = 0 ;i<10 ;i++) { arr.push_back (rand ()%100 ); } bubble (arr); show (arr); }
冒泡排序就是像气泡一样,慢慢往下沉或慢慢往上浮,j循环是数组的大小-i-1;对于函数传参,我们如果定义普通的数组的话,传(int arr[],int n),数组名就是一个指针。 复杂度:外层循环O(n),内层循环也是一个O(n),O(n方) 为什么j是size-1-i,因为a[j]要和a[j+1]来比较,所以有-1 ,每一次排序都会排好一个数,所以-i,第一次没排好,第二次排了1个少循环一次。
选择排序 1 2 3 4 5 6 7 8 9 10 11 12 void SelectSort (vector<int > &arr) { for (int i = 0 ;i<arr.size ();i++) { int maxIndex = i; for (int j = i;j<arr.size ();j++) { if (arr[j]>arr[maxIndex]) maxIndex = j; } swap (arr[i],arr[maxIndex]); } }
存放最大值/最小值的 下标,比较结束后用下标来索引交换。 复杂度O(n)O(n),是一个*不稳定的排序算法 //5 5 3,第一个5和3交换后这个5跑去后面了。
插入排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void InsertSort (vector<int > &arr) { for (int i = 1 ;i<arr.size ();i++) { int j = i-1 ; int val = arr[i]; for (;j>=0 ;j--) { if (val>=arr[j]) { break ; } arr[j+1 ] = arr[j]; } arr[j+1 ] = val; } }
为什么i要从1开始?因为之前的序列默认有序了,定义j在外面是为下面能成功访问j。 时间复杂度最坏O(n)*O(n),最好O(n);空间O(1)。稳定性好,
希尔排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void InsertSort (vector<int > &arr) { for (int gap = arr.size ()/2 ;gap>0 ;gap/=2 ) { for (int i = gap;i<arr.size ();i++) { int val = arr[i]; int j = i-gap; for (;j>=0 ;j-=gap) { if (val>=arr[j]) { break ; } arr[j+gap] = arr[j]; } arr[j+gap] = val; } } }
时间复杂度O(n)*O(n)最坏,最好O(n),不稳定。
快速排序 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 int Partition (vector<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 (vector<int > &arr,int begin,int end) { if (begin >= end) return ; int pos = Partition (arr,begin,end); QuickSort (arr,begin,pos-1 ); QuickSort (arr,pos+1 ,end); } QuickSort (a,0 ,a.size ()-1 );
函数传参的时候是要传起始位置,而不是具体的a[0]。 时间复杂度O(nlogn),空间复杂度O(logn) 二叉树递归所占用的栈内存 是不稳定 的排序
快速排序的优化
排的越来越有序的时候,找一个合适的地方调用插入排序,因为插入排序是效率最高的当趋于有序的时候
采用三数取中 法,L,R,Mid((l+r)/),取这三个数值在中间的数作为基准数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int medianOfThree (vector<int >& arr, int l, int r) { int mid = l + (r - l) / 2 ; if (arr[l] > arr[mid]) swap (arr[l], arr[mid]); if (arr[l] > arr[r]) swap (arr[l], arr[r]); if (arr[mid] > arr[r]) swap (arr[mid], arr[r]); swap (arr[l], arr[mid]); return arr[l]; } int Partition (vector<int >& arr, int l, int r) { int val = medianOfThree (arr, l, r); while (l < r) { while (l < r && arr[r] > val) r--; if (l < r) arr[l++] = arr[r]; while (l < r && arr[l] < val) l++; if (l < r) arr[r--] = arr[l]; } arr[l] = val; return l; }
优化后的算法
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 const int INSERTION_SORT_THRESHOLD = 16 ;int medianOfThree (vector<int >& arr, int l, int r) { int mid = l + (r - l) / 2 ; if (arr[l] > arr[mid]) swap (arr[l], arr[mid]); if (arr[l] > arr[r]) swap (arr[l], arr[r]); if (arr[mid] > arr[r]) swap (arr[mid], arr[r]); swap (arr[l], arr[mid]); return arr[l]; } int Partition (vector<int >& arr, int l, int r) { int val = medianOfThree (arr, l, r); while (l < r) { while (l < r && arr[r] > val) r--; if (l < r) arr[l++] = arr[r]; while (l < r && arr[l] < val) l++; if (l < r) arr[r--] = arr[l]; } arr[l] = val; return l; } void InsertionSort (vector<int >& arr, int l, int r) { for (int i = l + 1 ; i <= r; ++i) { int key = arr[i], j = i - 1 ; while (j >= l && arr[j] > key) { arr[j + 1 ] = arr[j]; j--; } arr[j + 1 ] = key; } } void QuickSort (vector<int >& arr, int begin, int end) { if (end - begin + 1 <= INSERTION_SORT_THRESHOLD) return ; int pos = Partition (arr, begin, end); QuickSort (arr, begin, pos - 1 ); QuickSort (arr, pos + 1 , end); } void HybridSort (vector<int >& arr) { QuickSort (arr, 0 , arr.size () - 1 ); InsertionSort (arr, 0 , arr.size () - 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 void Merge (vector <int > &arr,int l,int mid,int r) { int *p = new int [r-l+1 ]; int index = 0 ; int i = l; int j = mid + 1 ; while (i<=mid&&j<=r) { if (arr[i]<=arr[j]) { p[index++] = arr[i++]; } else { p[index++] = arr[j++]; } } while (i <= mid) { p[index++] = arr[i++]; } while (j <= r) { p[index++] = arr[j++]; } for (i = l,j = 0 ;i <=r;i++,j++) { arr[i] = p[j]; } delete []p; } void MergeSort (vector<int > &arr,int begin,int end) { if (begin>=end) return ; int mid = (begin + end)/2 ; MergeSort (arr,begin,mid); MergeSort (arr,mid + 1 ,end); Merge (arr,begin,mid,end); } void MergeSort (vector<int > &arr) { MergeSort (arr,0 ,arr.size ()-1 ); }
是一个稳定的算法
堆排序 二叉堆&大根堆&小根堆 完全二叉树的最后一层叶子节点 靠左排列,用数组存放的数在逻辑上可以被视为完全二叉树,2i+ 1和2i+2 大根堆和小根堆只是基于二叉堆的基础规定了当前节点和两个孩子节点值的大小关系。
叶子节点(树枝的末端,没有子节点)找到第一个非叶子节点:(末位元素下标-1)/2
大根堆:arr[i] > arr[i2+1]&&arr[i] > arr[i 2+2]
小根堆:arr[i] < arr[i2+1]&&arr[i] < arr[i 2+2] 操作堆的时候像队列/栈一样,只能操作堆顶元素
STL里的sort算法用的是什么排序? 快速排序 + 插入排序 (32的时候转为插入) + 堆排序(递归深度过深)
快速排序的时间复杂度不是稳定的nlogn,如何解决恶化问题 1.转插入排序 2.三数取中,选择合适的基准数
递归过深会引发什么问题? 函数开销变大,导致栈内存溢出,程序挂掉
怎么控制递归深度?如果达到递归深度了还没排完序怎么办? 转换成非递归的排序方式,如堆排序,好坏都是nlogn。
二叉堆代码 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 #include <iostream> #include <functional> #include <time.h> using namespace std;class PriorityQueue { public : using Comp = function<bool (int ,int )>; PriorityQueue (int cap = 20 ,Comp comp = 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_]; for (int i = 0 ;i<cap_;i++) { p[i] = que_[i]; } delete []que_; que_ = p; cap_ = 2 *cap_; } if (size_==0 ) { que_[size_] = val; } else { ShiftUp (size_,val); } size_++; } void pop () { if (size_ == 0 ) { throw "The container is empty" ; } size_--; if (size_ > 0 ) { ShiftDown (0 ,que_[size_]); } } bool empty () const {return size_==0 ;} int top () const { return que_[0 ]; } int size () { 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 (i<size_/2 ) { 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 (0 )); for (int i = 0 ;i < 30 ;i++) { que.push (rand ()%100 ); } while (!que.empty ()) { cout<<que.top ()<<" " ; que.pop (); } cout<<endl; }
哈希表 快速的查询时要第一时间想到哈希表。
有序的关联容器:set\map (红黑树实现,O(logn))
无序的关联容器:unordered_set\unordered_map (哈希表O(1))
通过哈希函数(除留余数法实现)
4.设置哈希表装载因子(已用个数/容量)
一般是0.75,超过了0.75后就要将哈希表扩容 ,扩容会从素数表里面找下一个质数作为新的容量,原来哈希表中的元素需要在新的哈希表中重新哈希
查找 未发生哈希冲突 O(1) 比如18%7 = 4,去看arr[4]有无18;如果发生了哈希冲突,就要往后去遍历,O(n)可能是线性探测. 代码实现
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 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 #include <iostream> using namespace std;enum State { STATE_UNUSE, STATE_DELETE, STATE_USING, }; struct Bucket { Bucket (int key = 0 ,State state = STATE_UNUSE) :key_ (key),state_ (state){} int key_; State state_; }; class HashTable { public : HashTable (int size = primes_[0 ],double LoadFactor = 0.75 ) :useBucketNum_ (0 ) ,loadFactor_ (LoadFactor) ,primeIdx_ (0 ) { if (size != primes_[0 ]) { for (;primeIdx_<PRIME_SIZE;primeIdx_++) { if (primes_[primeIdx_]>size) { break ; } if (primeIdx_ == PRIME_SIZE) { primeIdx_--; } } } tableSize_ = primes_[primeIdx_]; table_ = new Bucket[tableSize_]; } ~HashTable () { delete []table_; table_ = nullptr ; } public : bool insert (int key) { double factor = useBucketNum_*1.0 / tableSize_; cout<<"factor:" <<factor<<endl; if (factor > loadFactor_) { expand (); } int idx = key % tableSize_; int i = idx; do { if (table_[i].state_!=STATE_USING) { table_[i].state_ = STATE_USING; table_[i].key_ = key; useBucketNum_++; return true ; } i = (i + 1 )%tableSize_; }while (i!=idx); return false ; } bool erase (int key) { int idx = key % tableSize_; int i = idx; do { if (table_[i].state_==STATE_USING&&table_[i].key_==key) { table_[i].state_ = STATE_DELETE; useBucketNum_--; } i = (i+1 )%tableSize_; }while (table_[i].state_!=STATE_UNUSE&&i !=idx ); return true ; } bool find (int key) { int idx = key % tableSize_; int i = idx; do { if (table_[i].state_==STATE_USING&&table_[i].key_==key) { return true ; } i = (i+1 )%tableSize_; }while (table_[i].state_!=STATE_UNUSE && i != idx); return false ; } private : void expand () { ++primeIdx_; if (primeIdx_ == PRIME_SIZE) { throw "HashTable is large enought and it can't be expanded" ; } Bucket *newTable = new Bucket[primes_[primeIdx_]]; for (int i = 0 ;i<tableSize_;i++) { if (table_[i].state_==STATE_USING) { int idx = table_[i].key_ % primes_[primeIdx_]; int k = idx; do { if (newTable[k].state_!=STATE_USING) { newTable[k].state_ = STATE_USING; newTable[k].key_ = table_[i].key_; break ; } k = (k+1 ) % primes_[primeIdx_]; } while (k!=idx); } } delete []table_; table_ = newTable; tableSize_ = primes_[primeIdx_]; } private : Bucket *table_; int tableSize_; int useBucketNum_; double loadFactor_; static const int PRIME_SIZE = 10 ; static int primes_[PRIME_SIZE]; int primeIdx_; }; int HashTable::primes_[PRIME_SIZE] = {3 ,7 ,23 ,47 ,97 ,251 ,443 ,911 ,1471 ,42773 };int main () { HashTable htable; htable.insert (21 ); htable.insert (32 ); htable.insert (14 ); htable.insert (15 ); htable.insert (27 ); cout<<htable.find (14 )<<endl; htable.erase (14 ); cout<<htable.find (14 ); }
哈希表的应用 查重 查重或者统计重复的次数,查询的效率高 但是占用内存空间较大 。 找第一个重复出现的数字 用unordered_set来查,先遍历数组,然后用find函数去查询是否哈希表里是否存在有这个数(find返回的是迭代器的值,如果没找到就返回哈希表的末尾)如果没有就会insert,有的话就会输出。切记,在map和set这里尽量都用auto,不要用下标访问。找第一个和找所有的其实就是加不加break
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int main () { vector <int > a; srand (time (0 )); for (int i = 0 ;i < 10000 ;i++) { a.push_back (rand ()%10000 ); } unordered_set <int > s; for (auto val : a) { auto it = s.find (val); if (it!=s.end ()) { cout<<*it; break ; } else s.insert (val); } }
找所有重复出现的元素以及重复的次数,用unordered_map,没找到,就把这个数字插入(用emplace方法),找到了,迭代器的second++(值++);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int main () { vector <int > a; srand (time (0 )); for (int i = 0 ;i < 10000 ;i++) { a.push_back (rand ()%10000 ); } unordered_map <int ,int > m; for (auto p : a) { m[p]++; } for (auto q : m) { if (q.second>1 ) { cout<<q.first<<" " <<q.second<<endl; } } }
Top K问题 top K问题:大小根堆来过滤,大根堆过滤前top k小的数据;小根堆过滤前top k大的数据 用大根堆 来找前k个最小的元素;用小根堆 来找前k个最大的元素。 64 45 52 80 66 68 0 2 18 75 这十个先把前三个数组成一个大根堆,然后到第四个80,80比堆顶元素64大,说明一定比这个堆里的所有元素都大,所以往下继续找,一直到0,0比堆顶元素小,我们就调整,依次这样。 最后复杂度为logk * O(n)但是logk是常数级,所以最终我们以O(n)复杂度的找到topK元素。
快排分割法。
二叉树 BST树(Binary Search Tree 二叉搜索树)
对于二叉树上的每一个节点:左孩子的值<父节点的值<右孩子的值
每一层的节点最多的个数2^(L-1),第三层就是4个,第四层就是8个;
所有节点的个数N与层数的关系:等比数列求和 = 2^L-1 = N,2^L = N+1,两边取对数L = log2N
BST的删除操作
1.没有孩子的节点 父节点地址域为nullptr
2.有一个孩子 孩子写入父节点地址域
3.删除的节点有两个孩子 :找待删除 节点的前驱节点或后继节点 ,用前驱/后继将要删除的节点值覆盖掉,然后直接删除前驱/后继就可以了。 前驱:左子树里最大的 后继:右子树里最小的
二叉树的递归操作 左右根
基于后序遍历,使用递归操作来判断树的层数 int GetL(Node *node) { if (node!=nullptr) { PostOrder(node->left); PostOrder(node->right); return left > right ? left + 1: right + 1; } }
基于后序遍历递归操作求层数,可以用来求节点的个数 int num(Node *node) { if (node == nullptr) return 0; int left = num(node->left); int right = num(node->right); return left + right +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 #include <iostream> using namespace std;void func (int arr[], int i, int length) { if (i == length) { for (int j = 0 ; j < length; j++) { cout << arr[j] << " " ; } cout << endl; } else { func (arr, i + 1 , length); func (arr, i + 1 , length); } } int main () { int arr[] = { 1 ,2 ,3 }; int len = sizeof (arr) / sizeof (arr[0 ]); func (arr, 0 , len); return 0 ; }
子集树,其实就是递归选路径,需要一个辅助数组子集树
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 void func (int arr[], int i,int length, int x[]) { if (i == length) { for (int j = 0 ; j < length; j++) { if (x[j]==1 ) cout << arr[j] << " " ; } cout << endl; } else { for (int k = 1 ;k>=0 ;k--) { x[i] = k; func (arr,i+1 ,length,x); } } }