Category Archives: Programming

由前序遍历和中序遍历重构二叉树w

#include <iostream>
#include <vector>

using namespace std;

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

TreeNode * rebin(vector<int> pre, vector<int> in) {
    // 首先看看有米有东西吧
    // 一个元素都没有的话
    // 这个二叉树就只有一个NULL节点
    // 直接返回即可w
    if (pre.size() == 0) return nullptr;
    
    // 根据前序遍历的定义 根节点一定在前序遍历结果的第一个位置上
    // 而中序遍历的话 根结点必然需要等到它的左子树都输出完才会出现
    // 利用这一点我们分开左右子树
    int root_value = pre[0];
    TreeNode * root = new TreeNode(root_value);
    // 找到根节点在中序遍历里的下标
    // 以此分开左右子树
    int root_index_of_in = 0;
    for (int i = 0; i < in.size(); i++) {
        if (in[i] == root_value) {
            root_index_of_in = i;
            break;
        }
    }
    // 分开左右子树
    // 按原始的顺序分别保存左右子树的前序遍历和中序遍历
    vector<int> left_pre, left_in, right_pre, right_in;
    // 先是左子树
    for (int i = 0; i < root_index_of_in; i++) {
        // 这里需要+1
        // 因为前序遍历的话 第一个元素是根节点
        // 所以需要跳过当前(下标为0)的这个元素
        left_pre.emplace_back(pre[i+1]);
        left_in.emplace_back(in[i]);
    }
    // 然后是右子树
    for (int i = root_index_of_in + 1; i < in.size(); i++) {
        right_pre.emplace_back(pre[i]);
        right_in.emplace_back(in[i]);
    }
    // 既然左右子树的前序遍历和中序遍历都有了
    // 和我们一开始拿到的初始条件很相似吧~
    // 那么递归吧w
    root->left = rebin(left_pre, left_in);
    root->right = rebin(right_pre, right_in);
    return root;
}

// 三种遍历方式~/
typedef enum {
    PRE_ORDER,
    IN_ORDER,
    POST_ORDER,
} TreeTraversal;

void print_with_order(TreeNode * root, TreeTraversal order) {
    // 如果根节点是空的
    // 那啥都不用说直接返回吧
    if (root == nullptr) return;
    
    // 看看使用什么序来遍历
    // 本质上仅仅只是输出当前root节点的位置不一样
    if (order == PRE_ORDER) {
        cout << root->val << ' ';
        print_with_order(root->left, order);
        print_with_order(root->right, order);
    } else if (order == IN_ORDER) {
        print_with_order(root->left, order);
        cout << root->val << ' ';
        print_with_order(root->right, order);
    } else {
        print_with_order(root->left, order);
        print_with_order(root->right, order);
        cout << root->val << ' ';
    }
}

int main(int argc, const char * argv[]) {
    vector<int> pre = {1,2,4,7,3,5,6,8};
    vector<int> in = {4,7,2,1,5,3,8,6};
    TreeNode * head = rebin(pre, in);
    
    // 用构建好的树输出前序和中序遍历来验证~
    cout << "pre: ";
    print_with_order(head, PRE_ORDER);
    cout << endl;
    
    cout << "in: ";
    print_with_order(head, IN_ORDER);
    cout << endl;
}

探索网易云音乐——用 Python 写个爬虫吧w

既然想到要写点什么东西的话,那么就来试试网易云音乐吧w

那么第一步,我们要有数据才行,网易也当然不会公开自己的数据库啦,于是我们得用爬虫才行~

Python 语言里可以选择的爬虫还是不少,比如 Scrapy,然后在一开始开开心心的选择了 scrapy,之后发现网易云音乐对于 IP 的访问频率有限制,一旦超过限制,就会触发网易云的保护机制,暂时进入网易云黑名单,此时再访问的话,服务器返回的就都是 HTTP 503 - Service Unavailable。

解决方法说简单也简单,说麻烦也麻烦。简单在于,我的网络是电信的,于是只要重新拨号就能拿到新的公网 IP,写重新拨号的脚本也很简单。而稍有麻烦的地方在于,scrapy 是多线程的,在自己处理网易云返回 503 的时候,要注意重新拨号的脚本只需要执行一次。

如下图所示,假设我们的爬虫是 4 个线程,线程 2 在 t1 时刻访问时,遇到了 HTTP 503 之后,它就会进入我们处理网易云音乐 HTTP 503 的 handler。而对于其他线程来说,如果在 t1 时刻之前就开始访问了的话,那么是没有问题的,比如线程 1;如果是在 t1 时刻之后的话,比如线程 3、4,那么它们也会遇到 HTTP 503,但是这个时候,线程 3、4 就不用再走一次 handler,而是等待然后重试就可以。

HTTP 503 Handler
HTTP 503 Handler

但是也不用这么费劲,既然网易云音乐有频率限制,使用多线程的话,反而会更快的触发网易云的黑名单机制,因此,顺便作为重造轮子,我们来自己写个爬虫吧/

Continue reading 探索网易云音乐——用 Python 写个爬虫吧w

「Single Image Haze Removal Using Dark Channel Prior」Python Implementation with OpenCV

Haze Removed via Dark Channel Prior
Haze Removed via Dark Channel Prior

A Brief Review


Single Image Haze Removal Using Dark Channel Prior」是 2009 年 CVPR 最佳论文,何凯明博士在这篇论文中提出了暗通道先验的图像去雾算法。

那么暗通道先验是什么呢?这种方法是基于这样的一个观察:

It is based on a key observation most local patches in haze-free outdoor images contain some pixels which have very low intensities in at least one color channel.

简单来说就是对绝大多数没有雾的图像来说,它们的一些局部区域的像素中,某些像素至少有一个颜色通道的值很低。或者对应于原文的「low intensities」,即光强度很低。

我们需要对图像去雾的原因主要是图像中的雾会给很多算法带来麻烦,比如物体识别,特征提取等,它们都默认输入的图像是清晰的,没有雾的。或者不考虑算法,对于在野外或者无人机上的监视摄像头,遇到有雾的场景也是经常的事,即使是人工监视也是需要去雾的。

顺便一提,利用暗通道先验的算法去雾,还可以得到不错的景深图。

Last, the haze removal can produce depth information and benefit many vision algorithms and advanced image editing. Haze or fog can be a useful depth clue for scene understanding. The bad haze image can be put to good use.

在有雾的图像中,一个广泛使用的成像数学模型如下

\begin{equation}
    \mathbf{I}(\mathbf{x})=\mathbf{J}(\mathbf{x})t(\mathbf{x})+\mathbf{A}(1-t(\mathbf{x}))\tag{1}
\end{equation}

我们可以简单的将$\mathbf{x}$理解为图像中的某一个位置,那么,$\mathbf{I}(\mathbf{x})$则是我们最终观察到的有雾的图像在该点的强度;$\mathbf{J}(\mathbf{x})$是在没有雾的情况下,该点应有的强度;$t(\mathbf{x})$是该点的透射率(the medium transmission describing the portion of the light that is not scat- tered and reaches the camera);最后,$\mathbf{A}$是全局大气光强(global atmospheric light)。

图像去雾的目标就是从一张有雾的图像$\mathbf{I}(\mathbf{x})$中,恢复出没有雾的图像$\mathbf{J}(\mathbf{x})$,透射率$t(\mathbf{x})$以及$\mathbf{A}$,全局大气光强(global atmospheric light)。

其中,我们又把$\mathbf{J}(\mathbf{x})t(\mathbf{x})$的结果叫做「直接衰减,direct attenuation」,这个应该比较好理解,就是原始位置反射的光,经过介质(如雾、空气中的其他颗粒)时发生的衰减。然后我们又把$\mathbf{A}(1-t(\mathbf{x}))$叫做Airlight,也就是(先前经过介质时的)散射光导致的色偏。

airlight-and-direct-attenuation

Continue reading 「Single Image Haze Removal Using Dark Channel Prior」Python Implementation with OpenCV

One simple means to defend Wi-Fi availability

前面写了 3 篇有关如何攻击 Wi-Fi 可用性的 post,于是这里也来简单的介绍一下自己所想的一个非常简单的、用于保护 Wi-Fi 可用性的思路。现实生活中的情况总是复杂的,不过个人认为下面这个思路在理论上来看是成立的(也或许实践中早有公司应用),没有过多的问题,当然这也有它自身的局限性,似乎还没有一个足够完美的方案(如果有的话,业界应该早就推广开了)。

我们已经提到过三种攻击,分别是 Deauthentication FloodBeacon FloodAuthentication Flood,这三种攻击都需要攻击者发送数量非常多的包才行。于是我们也可以反过来利用这一点。

FSPL From 2412 MHz to 2472 MHz
FSPL From 2412 MHz to 2472 MHz

Continue reading One simple means to defend Wi-Fi availability

IEEE 802.11 Denial-of-Service: Authentication Flood

这是另一个 IEEE 802.11 攻击的方法,算上前面两篇

就收集齐了 IEEE 802.11 中最简单的三种可用性攻击。这三种方法不涉及任何密码学的东西,仅仅需要靠近受害者的网络范围,然后嗅探到几个包就可以开始攻击,并且足以让被攻击的人完全用不了无线网络。

这篇 post 中所描述的 Authentication Flood 与第一个 Deauthentication Flood 相对应。Deauthentication Flood 是强制让一个 AP 上、已经建立 association 的 STA 断开,从而达到目的。而Authentication Flood 则是通过伪造一大堆请求认证的包(其实还有 Association Flood,与这篇 post 中的方法相似),来塞满 AP 中的 Authentication / Association table,这样,真正的用户只能等到这个表有空 entry 的时候才能和 AP 建立连接了。

Authentication Flood
Authentication Flood

Continue reading IEEE 802.11 Denial-of-Service: Authentication Flood

IEEE 802.11 Denial-of-Service: Deauthentication Attack

使用 WPA / WPA2 能让你的数据有更好的安全保障,但是攻击者可以利用基于802.11设计上的缺陷来对你进行拒绝服务攻击,也就是 deauthentication 攻击。攻击者首先需要知道你的 Wi-Fi 的 MAC 地址,然后伪装成你的身份给 AP (或者说路由器) 发送 deauthencation 包。这样一来,AP 在收到了来自“你”的 deauthencation 包之后,就会断开和你的连接。虽然仅仅是用这种方法并不能窃取到你的数据(结合其他方法有可能能破解 AP 的密码,但是不在本文的讨论范围之内),并且这个方法的目的也不是要窃听数据,而是让你连不上自家的 AP。如此一来,你也不得不拿出网线。而且不管你使用什么加密方式、密码强度有多高,甚至隐藏你的 SSID (毕竟隐藏 SSID 正如字面意义上的,它只是藏了 SSID,并不是隐藏了你和 AP 之间的数据包)都不行。

802.11 - 2012 标准 8.2.4.1.3 Type and Subtype fields 下面,列举了所有有效的 MAC 帧的 type 与 subtype 的组合,这里我们关注的 deauthentication 是 type 为 management 的帧。

Type valueb3 b2 Typedescription Subtype valueb7 b6 b5 b4 Subtype description
00 Management ... ...
00 Management 1100 Deauthentication
00 Management ... ...

Captured deauthentication packet
Captured deauthentication packet

Continue reading IEEE 802.11 Denial-of-Service: Deauthentication Attack