A. 面試必會八大排序演算法(Python)
一、插入排序
介紹
插入排序的基本操作就是將一個數據插入到已經排好序的有序數據中,從而得到一個新的、個數加一的有序數據。
演算法適用於少量數據的排序,時間復雜度為O(n^2)。
插入排演算法是穩定的排序方法。
步驟
①從第一個元素開始,該元素可以認為已經被排序
②取出下一個元素,在已經排序的元素序列中從後向前掃描
③如果該元素(已排序)大於新元素,將該元素移到下一位置
④重復步驟3,直到找到已排序的元素小於或者等於新元素的位置
⑤將新元素插入到該位置中
⑥重復步驟2
排序演示
演算法實現
二、冒泡排序
介紹
冒泡排序(Bubble Sort)是一種簡單的排序演算法,時間復雜度為O(n^2)。
它重復地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重復地進行直到沒有再需要交換,也就是說該數列已經排序完成。
這個演算法的名字由來是因為越小的元素會經由交換慢慢「浮」到數列的頂端。
原理
循環遍歷列表,每次循環找出循環最大的元素排在後面;
需要使用嵌套循環實現:外層循環控制總循環次數,內層循環負責每輪的循環比較。
步驟
①比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
②對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。
③針對所有的元素重復以上的步驟,除了最後一個。
④持續每次對越來越少的元素重復上面的步驟,直到沒有任何一對數字需要比較。
演算法實現:
三、快速排序
介紹
快速排序(Quicksort)是對冒泡排序的一種改進,借用了分治的思想,由C. A. R. Hoare在1962年提出。
基本思想
快速排序的基本思想是:挖坑填數 + 分治法。
首先選出一個軸值(pivot,也有叫基準的),通過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。
實現步驟
①從數列中挑出一個元素,稱為 「基準」(pivot);
②重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊);
③對所有兩個小數列重復第二步,直至各區間只有一個數。
排序演示
演算法實現
四、希爾排序
介紹
希爾排序(Shell Sort)是插入排序的一種,也是縮小增量排序,是直接插入排序演算法的一種更高效的改進版本。希爾排序是非穩定排序演算法,時間復雜度為:O(1.3n)。
希爾排序是基於插入排序的以下兩點性質而提出改進方法的:
·插入排序在對幾乎已經排好序的數據操作時, 效率高, 即可以達到線性排序的效率;
·但插入排序一般來說是低效的, 因為插入排序每次只能將數據移動一位。
基本思想
①希爾排序是把記錄按下標的一定量分組,對每組使用直接插入演算法排序;
②隨著增量逐漸減少,每組包1含的關鍵詞越來越多,當增量減至1時,整個文件恰被分成一組,演算法被終止。
排序演示
演算法實現
五、選擇排序
介紹
選擇排序(Selection sort)是一種簡單直觀的排序演算法,時間復雜度為Ο(n2)。
基本思想
選擇排序的基本思想:比較 + 交換。
第一趟,在待排序記錄r1 ~ r[n]中選出最小的記錄,將它與r1交換;
第二趟,在待排序記錄r2 ~ r[n]中選出最小的記錄,將它與r2交換;
以此類推,第 i 趟,在待排序記錄ri ~ r[n]中選出最小的記錄,將它與r[i]交換,使有序序列不斷增長直到全部排序完畢。
排序演示
選擇排序的示例動畫。紅色表示當前最小值,黃色表示已排序序列,藍色表示當前位置。
演算法實現
六、堆排序
介紹
堆排序(Heapsort)是指利用堆積樹(堆)這種數據結構所設計的一種排序演算法,它是選擇排序的一種。
利用數組的特點快速指定索引的元素。
基本思想
堆分為大根堆和小根堆,是完全二叉樹。
大根堆的要求是每個節點的值不大於其父節點的值,即A[PARENT[i]] >=A[i]。
在數組的非降序排序中,需要使用的就是大根堆,因為根據大根堆的要求可知,最大的值一定在堆頂。
排序演示
演算法實現
七、歸並排序
介紹
歸並排序(Merge sort)是建立在歸並操作上的一種有效的排序演算法。該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。
基本思想
歸並排序演算法是將兩個(或兩個以上)有序表合並成一個新的有序表,即把待排序序列分為若干個子序列,每個子序列是有序的。然後再把有序子序列合並為整體有序序列。
演算法思想
自上而下遞歸法(假如序列共有n個元素)
① 將序列每相鄰兩個數字進行歸並操作,形成 floor(n/2)個序列,排序後每個序列包含兩個元素;
② 將上述序列再次歸並,形成 floor(n/4)個序列,每個序列包含四個元素;
③ 重復步驟②,直到所有元素排序完畢。
自下而上迭代法
① 申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合並後的序列;
② 設定兩個指針,最初位置分別為兩個已經排序序列的起始位置;
③ 比較兩個指針所指向的元素,選擇相對小的元素放入到合並空間,並移動指針到下一位置;
④ 重復步驟③直到某一指針達到序列尾;
⑤ 將另一序列剩下的所有元素直接復制到合並序列尾。
排序演示
演算法實現
八、基數排序
介紹
基數排序(Radix Sort)屬於「分配式排序」,又稱為「桶子法」。
基數排序法是屬於穩定性的排序,其時間復雜度為O (nlog(r)m) ,其中 r 為採取的基數,而m為堆數。
在某些時候,基數排序法的效率高於其他的穩定性排序法。
基本思想
將所有待比較數值(正整數)統一為同樣的數位長度,數位較短的數前面補零。然後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後,數列就變成一個有序序列。
基數排序按照優先從高位或低位來排序有兩種實現方案:
MSD(Most significant digital) 從最左側高位開始進行排序。先按k1排序分組, 同一組中記錄, 關鍵碼k1相等,再對各組按k2排序分成子組, 之後, 對後面的關鍵碼繼續這樣的排序分組, 直到按最次位關鍵碼kd對各子組排序後. 再將各組連接起來,便得到一個有序序列。MSD方式適用於位數多的序列。
LSD (Least significant digital)從最右側低位開始進行排序。先從kd開始排序,再對kd-1進行排序,依次重復,直到對k1排序後便得到一個有序序列。LSD方式適用於位數少的序列。
排序效果
演算法實現
九、總結
各種排序的穩定性、時間復雜度、空間復雜度的總結:
平方階O(n²)排序:各類簡單排序:直接插入、直接選擇和冒泡排序;
從時間復雜度來說:
線性對數階O(nlog₂n)排序:快速排序、堆排序和歸並排序;
O(n1+§))排序,§是介於0和1之間的常數:希爾排序 ;
線性階O(n)排序:基數排序,此外還有桶、箱排序。
B. 面試最常考的 100 道演算法題分類整理
大家好,我是 「負雪明早沖燭」 ,一位用 7 年寫槐毀了 1000 篇 LeetCode 演算法題題解的程序員。歡迎關注。
粉絲常說: LeetCode 演算法題太多了,准備面試該刷哪些題目 ?
我之前根據 LeetCode 上面的點贊量分享過: LeetCode 上最經典的 100 道演算法題 。
這 100 道題目都屬於經典題目了,面試也常考,不過我還是不放心呢,畢竟 經典題 ≠ 面試題 呀!
但如果想知道面試常考的 100 道演算法題的話,需要至少整理 1000 篇面經吧?這個工作量可不小啊!
還好,網上有個開源項目,幫我們做了這件事情,這個項目就是 CodeTop !
這是網站的界面(地址: https://codetop.cc/home ),展示的就是每個面試題目出現的頻度情況,甚至區分了公司和崗位:
這是開源項目的 GitHub 主頁,已經 11.5k star ⭐️ 了:
這個項目中的題目來源是牛客網的面經、網友投票等,而且持續更新中,所以還是比較可靠的。
我對這個項目做了陸明殲整理,分類整理出來面試常考的 100 道演算法題。
在整理之後,我對結果還是有點 驚訝 的!因為一些常見的數據結構與演算法,竟然沒有在常考面試中出現過!
比如前綴和、前綴樹、並查集、圖,這些都沒有出現……
最常考面試題還是很基本的鏈表、二叉樹、動態規劃等等,是不是符合你的認知呢?
強烈建議大家在面試前把這 100 道題目搞懂!
作為寵粉達人,我提供了 3 種方式查看這 100 道題目:
沒有任何套路,直接分享給大家!
在線查看地址: https://www.mubucm.com/doc/7jiBYKCKqet
在線查看地址: https://leetcode-cn.com/problem-list/q3iOID0B/
所有題目的地址如下:
前序遍歷
中序遍歷
層序遍歷
視圖
如果你覺得對你有幫助的話,求贊、求分享、求收藏。你的每一點鼓勵都是對我的最大幫助!
C. 面試會出哪些經典演算法題
如下:
1、排序演算法∶快速排序、歸並排序、計數排序
2、搜索演算法∶回溯、遞歸、剪枝技巧
3、圖論∶最短路、最小生成樹、網路流建模
4、動態規劃:背包問題、最長子序列、計數問題
5、基礎技巧:分治、倍增、二分、貪心
6、數組與鏈表:單/雙向鏈表、跳舞鏈
7、棧與隊列
8、樹與圖:最近公共祖先、並查集
9、哈希表
10、堆:大/小根堆、可並堆
11、字元串∶字典樹、後綴樹
演算法簡介:
演算法(Algorithm)是指解題方案的准確而完整的描述,是一系列解決問題的清晰指令,演算法代表著用系統的方法描述解決問題的策略機制。也就是說,能夠對一定規范的輸入,在有限時間內獲得所要求的輸出。
如果一個演算法有缺陷,或不適合於某個問題,執行這個演算法將不會解決這個問題。不同的演算法可能用不同的時間、空間或效率來完成同樣的任務。一個演算法的優劣可以用空間復雜度與時間復雜度來衡量。
演算法中的指令描述的是一個計算,當其運行時能從一個初始狀態和(可能為空的)初始輸入開始,經過一系列有限而清晰定義的狀態,最終產生輸出並停止於一個終態。一個狀態到另一個狀態的轉移不一定是確定的。隨機化演算法在內的一些演算法,包含了一些隨機輸入。
形式化演算法的概念部分源自嘗試解決希爾伯特提出的判定問題,並在其後嘗試定義有效計算性或者有效方法中成形。
這些嘗試包括庫爾特·哥德爾、Jacques Herbrand和斯蒂芬·科爾·克萊尼分別於1930年、1934年和1935年提出的遞歸函數,阿隆佐·邱奇於1936年提出的λ演算,1936年Emil Leon Post的Formulation 1和艾倫·圖靈1937年提出的圖靈機。即使在當前,依然常有直覺想法難以定義為形式化演算法的情況。
D. 求解公務員面試成績演算法
嗯。。
第一名的筆試成績折算:119.3分/2×60%=35。8分
你的筆試成績折算:97.5分/2×60%=29.3分
你們筆試成績相差:侍高6.5分
按面試老肆尺成績的比例折算你與第一名之間的分數差:6.5/雹旅40%=16.25分
由此來看,你的面試成績比第一名多16.25分就能勝他。
祝你成功!·
E. 測試開發面試必知演算法
測試開發的技能之一就是需要掌握一些開發的語言,而針對於考察開發語言,業界內比較容易採用的方式就是考察各種演算法。在此做一個簡單的總結(最近比較喜歡玩Python,所以都是以Python為例子,其它的語言類推。)
冒泡排序
冒泡排序演算法的運作如下:(從後往前)
比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。
針對所有的元素重復以上的步驟,除了最後一個。
持續每次對越來越少的元素重復上面的步驟,直到沒有任何一對數字需要比較。
實例:對列表 [2, 8, 4, 7, 5, 9, 0]進行冒泡排序
遞歸
遞歸過程一般通過函數或子過程來實現。遞歸方法:在函數或子過程的內部,直接或者間接地調用自己的演算法。
實例:要計算1-10的10位數字的乘積,直觀的演算法是1 2 3 4 5 6 7 8 9,利用遞歸則思路是循環執行n*n-1,直到n=1時
二叉樹遍歷演算法
從二叉樹的遞歸定義可知,一棵非空的二叉樹由根結點及左、右子樹這三個基本部分組成。因此,在任一給定結點上,可以按某種次序執行三個操作:
⑴訪問結點本身(N),
⑵遍歷該結點的左子樹(L),
⑶遍歷該結點的右子樹(R)。
以上三種操作有六種執行次序:
NLR、LNR、LRN、NRL、RNL、RLN。
二叉樹的節點表示可以使用
前序遍歷:根節點->左子樹->右子樹
中序遍歷:左子樹->根節點->右子樹
後序遍歷:左子樹->右子樹->根節點
實例:求二叉樹深度和寬度
求深度用遞歸;求寬度用隊列,然後把每層的寬度求出來,找出最大的就是二叉樹的寬度
字元串倒序輸出
思路一:索引的方法
思路二:借組列表進行翻轉
後續還有的話會繼續添加的。
F. 嵌入式工程師面試中常出現的演算法
嵌入式工程師面試中常出現的演算法
嵌入式系統是以應用為中心,以計算機技術為基礎,並且軟硬體可裁剪,適用於應用系統對功能、可靠性、成本、體積、功耗有嚴格要求的專用計算機系統。下面我為大家整理了關於嵌入式工程師面試中經常出現的演算法文章,希望對你有所幫助。
二分查找的代碼.
int bfind(int* a,int len,int val)
{
int m = len/2;
int l = 0;
int r = len;
while(l!=m && r!= m)
{
if(a[m] > val)
{
r = m;
m = (m+l)/2;
}
else if(a[m] < val)
{
l = m;
m = (m+r)/2;
}
else
return m;
}
return -1; //沒有找到
}
寫出在母串中查找子串出現次數的代碼.
int count1(char* str,char* s)
{
char* s1;
char* s2;
int count = 0;
while(*str!='')
{
s1 = str;
s2 = s;
while(*s2 == *s1&&(*s2!='')&&(*s1!='0'))
{
s2++;
s1++;
}
if(*s2 == '')
count++;
str++;
}
return count;
}
查找第一個匹配子串位置,如果返回的是s1長度len1表示沒有找到
size_t find(char* s1,char* s2)
{
size_t i=0;
size_t len1 = strlen(s1)
size_t len2 = strlen(s2);
if(len1-len2<0) return len1;
for(;i {
size_t m = i;
for(size_t j=0;j {
if(s1[m]!=s2[j])
break;
m++;
}
if(j==len)
break;
}
return i }
寫出快速排序或者某種排序演算法代碼
快速排序:
int partition(int* a,int l,int r)
{
int i=l-1,j=r,v=a[r];
while(1)
{
while(a[++i] while(a[--j]>v) if(j<=i) break;
if(i>=j)
break;
swap(a[i],a[j]);
}
swap(a[i],a[r]);
return i;
}
void qsort(int* a,int l,int r)
{
if(l>=r) return;
int i = partition(a,l,r);
qsort(a,l,i-1);
qsort(a,i+1,r);
}
冒泡排序:
void buble(int *a,int n)
{
for(int i=0;i {
for(int j=1;j {
if(a[j] {
int temp=a[j];
a[j] = a[j-1];
a[j-1] = temp;
}
}
}
}
插入排序:
void insertsort(int* a,int n)
{
int key;
for(int j=1;j {
key = a[j];
for(int i=j-1;i>=0&&a[i]>key;i--)
{
a[i+1] = a[i];
}
a[i+1] = key;
}
}
出現次數相當頻繁
實現strcmp函數
int strcmp11(char* l,char* r)
{
assert(l!=0&&r!=0);
while(*l == *r &&*l != '') l++,r++;
if(*l > *r)
return 1;
else if(*l == *r)
return 0;
return -1;
}
實現字元串翻轉
void reserve(char* str)
{
assert(str != NULL);
char * p1 = str;
char * p2 = str-1;
while(*++p2); //一般要求不能使用strlen
p2 -= 1;
while(p1 {
char c = *p1;
*p1++ = *p2;
*p2-- = c;
}
}
將一個單鏈表逆序
struct list_node
{
list_node(int a,list_node* b):data(a),next(b) //這個為了測試方便
{}
int data;
list_node* next;
};
void reserve(list_node* phead)
{
list_node* p = phead->next;
if(p == NULL || p->next == NULL) return; //只有頭節點或一個節點
list_node* p1=p->next;
p->next=NULL;
while(p1!=NULL)
{
p = p1->next;
p1->next = phead->next;
phead->next = p1;
p1 = p;
}
}
測試程序:
list lt;
lt.phead = new list_node(0,0);
lt.phead->next = new list_node(1,0);
lt.phead->next->next = new list_node(2,0);
lt.phead->next->next->next = new list_node(3,0);
lt.reserve();
list_node * p = lt.phead;
while(p)
{
coutnext;
}
循環鏈表的節點對換和刪除。
//雙向循環
list_node* earse(list_node* node)
{
// if(node == rear) return node->next; //對於頭節點可判斷也可不判斷。最好加上
list_node* next = node->next;
next->prev = node->prev;
node->prev->next = next;
delete node;
retrun next;
}
//單項循環
list_node* earse(list_node* node)
{
// if(node == rear) return node->next; //對於頭節點可判斷也可不判斷。最好加上
list_node* p = rear;
while(p->next != node) p=p->next;
p->next = node->next;
delete node;
retrun p->next;
}
將一個數字字元串轉換為數字."1234" -->1234
int atoii(char* s)
{
assert(s!=NULL);
int num = 0;
int temp;
while(*s>'0' && *s<'9')
{
num *= 10;
num += *s-'0';
s++;
}
return num;
}
出現次數相當頻繁
.實現任意長度的整數相加或者相乘功能。
void bigadd(char* num,char* str,int len)
{
for(int i=len;i>0;i--)
{
num[i] += str[i];
int j = i;
while(num[j]>=10)
{
num[j--] -= 10;
num[j] += 1;
}
}
}
.寫函數完成內存的拷貝
void* memcpy( void *dst, const void *src, unsigned int len )
{
register char *d;
register char *s;
if (len == 0)
return dst;
if ( dst > src ) //考慮覆蓋情況
{
d = (char *)dst + len - 1;
s = (char *)src + len - 1;
while ( len >= 4 ) //循環展開,提高執行效率
{
*d-- = *s--;
*d-- = *s--;
*d-- = *s--;
*d-- = *s--;
len -= 4;
}
while ( len-- )
{
*d-- = *s--;
}
}
else if ( dst < src )
{
d = (char *)dst;
s = (char *)src;
while ( len >= 4 )
{
*d++ = *s++;
*d++ = *s++;
*d++ = *s++;
*d++ = *s++;
len -= 4;
}
while ( len-- )
{
*d++ = *s++;
}
}
return dst;
}
出現次數相當頻繁
編寫類String的構造函數、析構函數和賦值函數,已知類String的原型為:
class String
{
public:
String(const char *str = NULL); // 普通構造函數
String(const String &other); // 拷貝構造函數
~ String(void); // 析構函數
String & operate =(const String &other); // 賦值函數
private:
char *m_data; // 用於保存字元串
};
解答:
//普通構造函數
String::String(const char *str)
{
if(str==NULL)
{
m_data = new char[1]; // 得分點:對空字元串自動申請存放結束標志''的`空
//加分點:對m_data加NULL 判斷
*m_data = '';
}
else
{
int length = strlen(str);
m_data = new char[length+1]; // 若能加 NULL 判斷則更好
strcpy(m_data, str);
}
}
// String的析構函數
String::~String(void)
{
delete [] m_data; // 或delete m_data;
}
//拷貝構造函數
String::String(const String &other) // 得分點:輸入參數為const型
{
int length = strlen(other.m_data);
m_data = new char[length+1]; //加分點:對m_data加NULL 判斷
strcpy(m_data, other.m_data);
}
//賦值函數
String & String::operate =(const String &other) // 得分點:輸入參數為const型
{
if(this == &other) //得分點:檢查自賦值
return *this;
delete [] m_data; //得分點:釋放原有的內存資源
int length = strlen( other.m_data );
m_data = new char[length+1]; //加分點:對m_data加NULL 判斷
strcpy( m_data, other.m_data );
return *this; //得分點:返回本對象的引用
}
剖析:
能夠准確無誤地編寫出String類的構造函數、拷貝構造函數、賦值函數和析構函數的面試者至少已經具備了C++基本功的60%以上!
在這個類中包括了指針類成員變數m_data,當類中包括指針類成員變數時,一定要重載其拷貝構造函數、賦值函數和析構函數,這既是對C++程序員的基本要求,也是《EffectiveC++》中特別強調的條款。
實現strcpy
char * strcpy( char *strDest, const char *strSrc )
{
assert( (strDest != NULL) && (strSrc != NULL) );
char *address = strDest;
while( (*strDest++ = * strSrc++) != ‘’ );
return address;
}
編寫一個函數,作用是把一個char組成的字元串循環右移n個。比如原來是“abcdefghi”如果n=2,移位後應該是“hiabcdefgh”
函數頭是這樣的:
//pStr是指向以''結尾的字元串的指針
//steps是要求移動的n
void LoopMove ( char * pStr, int steps )
{
//請填充...
}
解答:
正確解答1:
void LoopMove ( char *pStr, int steps )
{
int n = strlen( pStr ) - steps;
char tmp[MAX_LEN];
strcpy ( tmp, pStr + n );
strcpy ( tmp + steps, pStr);
*( tmp + strlen ( pStr ) ) = '';
strcpy( pStr, tmp );
}
正確解答2:
void LoopMove ( char *pStr, int steps )
{
int n = strlen( pStr ) - steps;
char tmp[MAX_LEN];
memcpy( tmp, pStr + n, steps );
memcpy(pStr + steps, pStr, n );
memcpy(pStr, tmp, steps );
}
;