ARTS 打卡week05
每周完成一个 ARTS:
Algorithm: 每周至少做一个 LeetCode 的算法题
Review: 阅读并点评至少一篇英文技术文章
Tips: 学习至少一个技术技巧
Share: 分享一篇有观点和思考的技术文章
Algorithm
4. 寻找两个正序数组的中位数
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。
请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例
# 示例一
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
# 示例二
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
解析
package leetCode
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
nums := combine(nums1, nums2)
return medianOf(nums)
}
func combine(mis, njs []int) []int {
lenMis, i := len(mis), 0
lenNjs, j := len(njs), 0
res := make([]int, lenMis+lenNjs)
for k := 0; k < lenMis+lenNjs; k++ {
if i == lenMis ||
(i < lenMis && j < lenNjs && mis[i] > njs[j]) {
res[k] = njs[j]
j++
continue
}
if j == lenNjs ||
(i < lenMis && j < lenNjs && mis[i] <= njs[j]) {
res[k] = mis[i]
i++
}
}
return res
}
func medianOf(nums []int) float64 {
l := len(nums)
if l == 0 {
panic("切片的长度为0,无法求解中位数。")
}
if l%2 == 0 {
return float64(nums[l/2]+nums[l/2-1]) / 2.0
}
return float64(nums[l/2])
}
常用排序算法分析
-
冒泡排序
解析
实现:
func BubbleSort(arr []int, len int) { for i := len - 1; i > 0; i-- { // 如果数组本身是有序的,遍历完每个元素直接跳出循环 flag := true for j := 0; j < i; j++ { if arr[j] > arr[j+1] { arr[j], arr[j+1] = arr[j+1], arr[j] flag = false } } if flag { break } } }
分析:
稳定性:稳定 时间复杂度: 最好情况:顺序T= O( N) 最坏情况:逆序T= O( N2) 空间复杂度:O(1)
-
选择排序
解析:
循环遍历序列,每次选择最小的元素放在该位置(如:第一次循环选择最小的元素放在下标为0的位置,第二次循环选择第二小的元素放在下标为1的位置,依次类推)
实现:
func SelectSort(arr []int, len int) { index := 0 for i := 0; i < len; i++ { for j := i + 1 ; j < len; j++ { if arr[index] > arr[j] { index = j } } arr[i], arr[index] = arr[index], arr[i] index = i + 1 } }
分析:
时间复杂度:O(N2) 空间复杂度:O(1) 稳定性:稳定
-
插入排序
解析:
实现:
func InsertSort(arr []int, len int) { i, j, tmp := 0, 0, 0 for i = 0; i < len; i++ { tmp = arr[i] for j = i; j > 0 && arr[j-1] > tmp; j-- { arr[j] = arr[j-1] } arr[j] = tmp } }
分析:
稳定性:稳定 时间复杂度: 最好情况:顺序T= O( N) 最坏情况:逆序T= O( N2) 空间复杂度:O(1)
冒泡排序和插入排序本质上属于交换排序,通过交换两个元素的位置实现排序,交换的次数等于序列中逆序对的个数,适用于那些基本有序的序列。
-
希尔排序
解析:使用希尔的原始序列
(N/2, N/4, ...1
作为算法中的间隔,假设序列为[9, 8, 3, 7, 5, 6, 4, 1]
实现:
func shell_sort(arr []int, len int) { d, i, j, tmp := 0, 0, 0, 0 for d = len/2; d > 0; d /= 2 { for i = d; i < len; i++ { // 插入最后的位置,再进行怕徐 tmp = arr[i] for j = i; j >= d && arr[j-d] > tmp; j-=d { arr[j] = arr[j-d] } arr[j] = tmp } } }
分析:
稳定性:不稳定(增量元素不为一) 时间复杂度:T= O( N2) 空间复杂度:O(1) 增量元素不互质,则小增量可能根本不起作用 其他增量序列:Hibbard 增量序列,Dk= 2ek–1 —相邻元素互质 时间复杂度(O(N5/4))
-
归并排序
分析:
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而**治(conquer)**的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
实现:// 合并有序子列 func Merge(A, B, tmp []int) { lenA, indexA, lenB, indexB, indexT := len(A), 0, len(B), 0, 0 for indexA < lenA && indexB < lenB { if A[indexA] < B[indexB] { tmp[indexT] = A[indexA] indexA++ } else { tmp[indexT] = B[indexB] indexA++ } indexT++ } for indexA < lenA { tmp[indexT] = A[indexA] indexA++ indexT++ } for indexB < lenB { tmp[indexT] = B[indexB] indexA++ indexT++ } for index, value := range tmp { A[index] = value } } // 两个有序子列放在同一个切片中有序合并 func MergeOne(A, tmpA []int, lstart, rstart, rend int) { lend := rstart - 1 // 这里主要是为了声明一个变量存放所有的中间变量 tmp := lstart num := rend - lstart + 1 for lstart <= lend && rstart <= rend { if A[lstart] <= A[rstart] { tmpA[tmp] = A[lstart] lstart++ } else { tmpA[tmp] = A[rstart] rstart++ } tmp++ } for i := 0; i < num; i++ { A[rend] = tmpA[rend] rend-- } } // 两个有序子列放在同一个切片中有序合并 func MergeTwo(A, tmpA []int, lstart, rstart, rend int) { lend := rstart - 1 // 这里主要是为了声明一个变量存放所有的中间变量 tmp := lstart for lstart <= lend && rstart <= rend { if A[lstart] <= A[rstart] { tmpA[tmp] = A[lstart] lstart++ } else { tmpA[tmp] = A[rstart] rstart++ } tmp++ } } // 递归实现 func MergeSortRecursor(arr, arrTmp []int, lstart, rend int) { center := (lstart + rend)/2 if lstart < rend { MergeSortRecursor(arr, arrTmp, lstart, center) MergeSortRecursor(arr, arrTmp, center+1, rend) MergeOne(arr, arrTmp, lstart, center+1, rend) } } func MergeSort( arr []int, len int){ arrTmp := make([]int, len) if arrTmp != nil { MergeSortRecursor(arr, arrTmp, 0, len-1) } } // 循环实现 func MergeSortLoop(arr, arrTmp []int, len, lens int) { i := 0 for i = 0; i <= len-2*lens; i += 2*lens { MergeTwo(arr, arrTmp, i, i+lens, i+lens-1) } if i + lens < len { MergeTwo(arr, arrTmp, i, i+lens, len-1) } else { for j := i; j < len; j++ { arrTmp[j] = arr[j] } } } func MergeSortL(arr []int, len int){ lens, arrTmp := 1, make([]int, len) if arrTmp != nil { for lens < len { MergeSortLoop(arr, arrTmp, len, lens) lens *= 2 MergeSortLoop(arrTmp, arr, len, lens) lens *= 2 } } else { fmt.Println("memory not available") } }
分析:
时间复杂度:T(N) = T(N/2) + T(N/2) + O(N) T( N) = O(NlogN) 空间复杂度:O(N) 适用于外排情况
参考资料:
-
堆排序
void Swap( ElementType *a, ElementType *b ) { ElementType t = *a; *a = *b; *b = t; } void PercDown( ElementType A[], int p, int N ) { /* 改编代码4.24的PercDown( MaxHeap H, int p ) */ /* 将N个元素的数组中以A[p]为根的子堆调整为最大堆 */ int Parent, Child; ElementType X; X = A[p]; /* 取出根结点存放的值 */ for( Parent=p; (Parent*2+1)<N; Parent=Child ) { Child = Parent * 2 + 1; if( (Child!=N-1) && (A[Child]<A[Child+1]) ) Child++; /* Child指向左右子结点的较大者 */ if( X >= A[Child] ) break; /* 找到了合适位置 */ else /* 下滤X */ A[Parent] = A[Child]; } A[Parent] = X; } void HeapSort( ElementType A[], int N ) { /* 堆排序 */ int i; for ( i=N/2-1; i>=0; i-- )/* 建立最大堆 */ PercDown( A, i, N ); for ( i=N-1; i>0; i-- ) { /* 删除最大堆顶 */ Swap( &A[0], &A[i] ); /* 见代码7.1 */ PercDown( A, 0, i ); } }
-
快速排序
在快排的过程中,每一次我们要取一个元素作为枢纽值(pivot),以这个数字来将序列划分为两部分。在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。
分析:
实现:
// default value
const CUTOFF = 0
// get median element
func median3(arr []int, left, right int) int {
center := (left + right) / 2
// swap order
if arr[left] > arr[right] {
arr[left], arr[right] = arr[right], arr[left]
}
if arr[left] > arr[center] {
arr[left], arr[center] = arr[center], arr[left]
}
if arr[center] > arr[right] {
arr[center], arr[right] = arr[right], arr[center]
}
// move median element into right-1 position
arr[center], arr[right-1] = arr[right-1], arr[center]
return arr[right-1]
}
func QuickSort(arr []int, left, right int) {
if (CUTOFF <= right - left) {
pivot := median3(arr, left, right)
i, j := left, right - 1
for {
// if arr[i], arr[j] == pivot, swap value
for ; arr[i] < pivot; i++ {}
for ; arr[j] > pivot; j++ {}
if i < j {
arr[i], arr[j] = arr[j], arr[i]
} else {
break
}
}
arr[i], arr[right-1] = arr[right-1], arr[i]
QuickSort(arr, left, i-1)
QuickSort(arr, i, right)
} else {
// shorter sequence use inserting sort
InsertSort(arr[left:], right-left+1)
}
}
分析:
时间复杂度:O(NlogN)
空间复杂度:O(logN)
参考资料:
-
基数排序
分析:
/* 基数排序 - 次位优先 */
/* 假设元素最多有MaxDigit个关键字,基数全是同样的Radix */
#define MaxDigit 4
#define Radix 10
/* 桶元素结点 */
typedef struct Node *PtrToNode;
struct Node {
int key;
PtrToNode next;
};
/* 桶头结点 */
struct HeadNode {
PtrToNode head, tail;
};
typedef struct HeadNode Bucket[Radix];
int GetDigit ( int X, int D )
{ /* 默认次位D=1, 主位D<=MaxDigit */
int d, i;
for (i=1; i<=D; i++) {
d = X % Radix;
X /= Radix;
}
return d;
}
void LSDRadixSort( ElementType A[], int N )
{ /* 基数排序 - 次位优先 */
int D, Di, i;
Bucket B;
PtrToNode tmp, p, List = NULL;
for (i=0; i<Radix; i++) /* 初始化每个桶为空链表 */
B[i].head = B[i].tail = NULL;
for (i=0; i<N; i++) { /* 将原始序列逆序存入初始链表List */
tmp = (PtrToNode)malloc(sizeof(struct Node));
tmp->key = A[i];
tmp->next = List;
List = tmp;
}
/* 下面开始排序 */
for (D=1; D<=MaxDigit; D++) { /* 对数据的每一位循环处理 */
/* 下面是分配的过程 */
p = List;
while (p) {
Di = GetDigit(p->key, D); /* 获得当前元素的当前位数字 */
/* 从List中摘除 */
tmp = p; p = p->next;
/* 插入B[Di]号桶尾 */
tmp->next = NULL;
if (B[Di].head == NULL)
B[Di].head = B[Di].tail = tmp;
else {
B[Di].tail->next = tmp;
B[Di].tail = tmp;
}
}
/* 下面是收集的过程 */
List = NULL;
for (Di=Radix-1; Di>=0; Di--) { /* 将每个桶的元素顺序收集入List */
if (B[Di].head) { /* 如果桶不为空 */
/* 整桶插入List表头 */
B[Di].tail->next = List;
List = B[Di].head;
B[Di].head = B[Di].tail = NULL; /* 清空桶 */
}
}
}
/* 将List倒入A[]并释放空间 */
for (i=0; i<N; i++) {
tmp = List;
List = List->next;
A[i] = tmp->key;
free(tmp);
}
}
/* 基数排序 - 主位优先 */
/* 假设元素最多有MaxDigit个关键字,基数全是同样的Radix */
#define MaxDigit 4
#define Radix 10
/* 桶元素结点 */
typedef struct Node *PtrToNode;
struct Node{
int key;
PtrToNode next;
};
/* 桶头结点 */
struct HeadNode {
PtrToNode head, tail;
};
typedef struct HeadNode Bucket[Radix];
int GetDigit ( int X, int D )
{ /* 默认次位D=1, 主位D<=MaxDigit */
int d, i;
for (i=1; i<=D; i++) {
d = X%Radix;
X /= Radix;
}
return d;
}
void MSD( ElementType A[], int L, int R, int D )
{ /* 核心递归函数: 对A[L]...A[R]的第D位数进行排序 */
int Di, i, j;
Bucket B;
PtrToNode tmp, p, List = NULL;
if (D==0) return; /* 递归终止条件 */
for (i=0; i<Radix; i++) /* 初始化每个桶为空链表 */
B[i].head = B[i].tail = NULL;
for (i=L; i<=R; i++) { /* 将原始序列逆序存入初始链表List */
tmp = (PtrToNode)malloc(sizeof(struct Node));
tmp->key = A[i];
tmp->next = List;
List = tmp;
}
/* 下面是分配的过程 */
p = List;
while (p) {
Di = GetDigit(p->key, D); /* 获得当前元素的当前位数字 */
/* 从List中摘除 */
tmp = p; p = p->next;
/* 插入B[Di]号桶 */
if (B[Di].head == NULL) B[Di].tail = tmp;
tmp->next = B[Di].head;
B[Di].head = tmp;
}
/* 下面是收集的过程 */
i = j = L; /* i, j记录当前要处理的A[]的左右端下标 */
for (Di=0; Di<Radix; Di++) { /* 对于每个桶 */
if (B[Di].head) { /* 将非空的桶整桶倒入A[], 递归排序 */
p = B[Di].head;
while (p) {
tmp = p;
p = p->next;
A[j++] = tmp->key;
free(tmp);
}
/* 递归对该桶数据排序, 位数减1 */
MSD(A, i, j-1, D-1);
i = j; /* 为下一个桶对应的A[]左端 */
}
}
}
void MSDRadixSort( ElementType A[], int N )
{ /* 统一接口 */
MSD(A, 0, N-1, MaxDigit);
}
- 排序算法性能比较
Review
Tips
What happens when you update your DNS?
-
dig命令
dig命令是常用的域名查询工具,可以用来测试域名系统工作是否正常。
-
语法
dig(选项)(参数)
-
选项
@<服务器地址>:指定进行域名解析的域名服务器; -b<ip地址>:当主机具有多个IP地址,指定使用本机的哪个IP地址向域名服务器发送域名查询请求; -f<文件名称>:指定dig以批处理的方式运行,指定的文件中保存着需要批处理查询的DNS任务信息; -P:指定域名服务器所使用端口号; -t<类型>:指定要查询的DNS数据类型; -x<IP地址>:执行逆向域名查询; -4:使用IPv4; -6:使用IPv6; -h:显示指令帮助信息。
-
参数
- 主机:指定要查询域名主机; - 查询类型:指定DNS查询的类型; - 查询类:指定查询DNS的class; - 查询选项:指定查询选项。
-
实例
[root@localhost ~]# dig www.linuxde.net ; <<>> DiG 9.3.6-P1-RedHat-9.3.6-20.P1.el5_8.1 <<>> www.linuxde.net ;; global options: printcmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2115 ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 2, ADDITIONAL: 0 ;; QUESTION SECTION: ;www.linuxde.net. IN A ;; ANSWER SECTION: www.linuxde.net. 0 IN CNAME host.1.linuxde.net. host.1.linuxde.net. 0 IN A 100.42.212.8 ;; AUTHORITY SECTION: linuxde.net. 8 IN NS f1g1ns2.dnspod.net. linuxde.net. 8 IN NS f1g1ns1.dnspod.net. ;; Query time: 0 msec ;; SERVER: 202.96.104.15#53(202.96.104.15) ;; WHEN: Thu Dec 26 11:14:37 2013 ;; MSG SIZE rcvd: 121
Share
-
迭代器模式
迭代器模式是比较常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要直到集合对象的底层表示,迭代器属于行为型模式。 -
介绍
意图:提供一种方法顺序访问一个聚合对象中的各个元素,而又无须暴露该对象的内部表示。主要解决:不同的方式来遍历整个集合对象。
何时使用:遍历一个聚合对象的时候。
-
优点:
1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
-
缺点:
由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
-
使用场景:
1、访问一个聚合对象的内容而无须暴露它的内部表示。 2、需要为聚合对象提供多种遍历方式。 3、为遍历不同的聚合结构提供一个统一的接口。
-
注意事项:
迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。
-
实现:
我们将创建一个叙述导航方法的 Iterator 接口和一个返回迭代器的 Container 接口。实现了 Container 接口的实体类将负责实现 Iterator 接口。
IteratorPatternDemo,我们的演示类使用实体类 NamesRepository 来打印 NamesRepository 中存储为集合的 Names。
-
代码实现
package IteratorPattern type SomeSlice interface{} type Iterator interface { HasNext() bool Next() SomeSlice } type Container interface { GetIterator() Iterator } // 这相当于一个容器 type NameRepository struct { } type NameIterator struct { names []string index int } func (s *NameRepository) GetIterator() Iterator { nameIterator := new(NameIterator) nameIterator.names = []string{"Robert", "John", "Julie", "Lora"} return nameIterator } func (s *NameIterator) HasNext() bool { if s.index < len(s.names) { return true } return false } func (s *NameIterator) Next() SomeSlice { if s.HasNext() { n := s.index s.index++ return s.names[n] } return nil }
-
测试:
func testIteratorPattern() { namesRepository := new(IteratorPattern.NameRepository) for iter := namesRepository.GetIterator(); iter.HasNext(); { fmt.Println(iter.Next()) } }
Summary
不要因为一时的困难而降低自己的标准,没有迎难而上的实力,可以停下脚步努力让自己变得更加优秀。等到实力足以冲破当前的困境的时候,再重现江湖。