ARTS 打卡第五周

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?

DNS(二)通过dig命令理解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

Golang迭代器设计模式(十六)

  • 迭代器模式
    迭代器模式是比较常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要直到集合对象的底层表示,迭代器属于行为型模式。

  • 介绍
    意图:提供一种方法顺序访问一个聚合对象中的各个元素,而又无须暴露该对象的内部表示。

    主要解决:不同的方式来遍历整个集合对象。

    何时使用:遍历一个聚合对象的时候。

  • 优点:

    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

不要因为一时的困难而降低自己的标准,没有迎难而上的实力,可以停下脚步努力让自己变得更加优秀。等到实力足以冲破当前的困境的时候,再重现江湖。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值