一个普通技术宅的点点滴滴

0%

树结构,或者说树状结构,相信很多人都不陌生,它的名称来自于以树的象征来表现出构造之间的关系,虽然在图象的呈现上,其为一个上下颠倒的树。以下为维基中对于树结构的定义。

在计算机科学中,树(英语:tree)是一种抽象数据类型(ADT)或是实作这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。它是由n(n>0)个有限节点组成一个具有层次关系的集合。

  • 节点的度:一个节点含有的子树的个数称为该节点的度;
  • 树的度:一棵树中,最大的节点的度称为树的度;
  • 叶节点或终端节点:度为零的节点;
  • 非终端节点或分支节点:度不为零的节点;
  • 父亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点;
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  • 深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
  • 高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;
  • 堂兄弟节点:父节点在同一层的节点互为堂兄弟;
  • 节点的祖先:从根到该节点所经分支上的所有节点;
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
  • 森林:由m(m>=0)棵互不相交的树的集合称为森林;

而每个节点最多含有两个子树的树又被称为二叉树,下图为一颗二叉树的结构
由Derrick Coetzee - 自己的作品,公有领域,https://commons.wikimedia.org/w/index.php?curid=488419

明白了二叉树以后,我们便可以来实现一种特殊的二叉树——“二叉堆”。
想象一下我们堆东西时的情景,大的物体堆在下层,小的堆在上层。二叉堆同理,父节点的键值总是保持固定的序关系于任何一个子节点的键值,且每个节点的左子树和右子树都是一个二叉堆。

当父节点的键值总是大于或等于任何一个子节点的键值时为最大堆,相反,当父节点的键值总是小于或等于任何一个子节点的键值时为最小堆。

二叉堆一般采用数组来实现,并且舍弃了下标为0的值,这样的话便可以满足关系:元素i的子节点为2i和2i+1。可以方便的遍历一个二叉堆。同时我们应该实现两个方法,让二叉堆在从堆顶移除元素和在堆底添加元素后,能够恢复二叉堆原来的性质。

我们一般将这两个方法命名为sink()和swim(),这个过程可以看作,减少一个元素,就要让最后的元素补上开头的位置,再让这个元素下沉(sink)到合适的位置,增加一个元素,就要在堆的最后插入元素,再将这个元素上浮(swim)到合适的位置。

利用我们所实现二叉堆的两个方法,我们可以实现一个优先队列,有限队列与队列有些像,但是其返回值永远为队列中的最大/最小值。目前已知的有限队列的实现方法中,二叉堆是最实用的,因为其将入列和出列操作都限制在O(nlgn)的时间复杂度之内。

以下为一个返回最大值的优先队列实现:

template <typename T>void swap(T &a, T&b)
{
  T temp = a;
  a = b;
  b = temp;
}

template <typename T>class MaxPQ
{
private:
  T *datas;
  int pqsize;
  int pqcount;
  void swim(int k);
  void sink(int k);
public:
  MaxPQ ();
  MaxPQ (int max);
  MaxPQ (T d[], int size);
  void insert(T v);
  T max();
  T delmax();
  bool isEmpty();
  int size();
  void resize(int size);
  virtual ~MaxPQ ();
};

template <typename T>void MaxPQ<T>::swim(int k)
{
  while (k > 1 && this->datas[k>>1] < this->datas[k])
  {
    swap(this->datas[k>>1],this->datas[k]);
    k>>=1;
  }
}

template <typename T>void MaxPQ<T>::sink(int k)
{
  while (k<<1 <= this->pqcount)
  {
    int j = k<<1;
    if (j < this->pqcount && this->datas[j+1] > this->datas[j])
    {
      j++;
    }
    if (this->datas[j] > this->datas[k])
    {
      swap(this->datas[j],this->datas[k]);
    }
    k = j;
  }
}

template <typename T>MaxPQ<T>::MaxPQ():pqsize(4),pqcount(0)
{
  this->datas = new T[pqsize];
}

template <typename T>MaxPQ<T>::MaxPQ(int max):pqsize(max),pqcount(0)
{
  this->datas = new T[pqsize];
}

template <typename T>MaxPQ<T>::MaxPQ(T d[], int size):pqsize(size+1),pqcount(0)
{
  this->datas = new T[pqsize];
  for (int i = 0;i < size;i++)
  {
    this->insert(d[i]);
  }
}

template <typename T>T MaxPQ<T>::max()
{
  if (this->pqcount > 0)
  {
    return this->datas[1];
  }
  return (T)NULL;
}

template <typename T>T MaxPQ<T>::delmax()
{
  if (this->pqcount > 0)
  {
    T max = this->datas[1];
    //this->pqcount--;
    if (this->pqcount > 0)
    {
      this->datas[1] = this->datas[pqcount];
      this->pqcount--;
      sink(1);
    }
    return max;
  }
  return (T)NULL;
}

template <typename T>bool MaxPQ<T>::isEmpty()
{
  return this->pqcount == 0;
}

template <typename T>int MaxPQ<T>::size()
{
  return this->pqcount;
}


template <typename T>void MaxPQ<T>::resize(int size)
{
  T *new_data = new T[size];
  for (int i = 0;i <= this->pqcount;i++)
  {
    new_data[i] = this->datas[i];
  }
  delete this->datas;
  this->datas = new_data;
}

template <typename T>void MaxPQ<T>::insert(T v)
{
  if (this->pqcount+1 >= this->pqsize)
  {
    pqsize <<= 1;
    this->resize(pqsize);
  }
  this->datas[++this->pqcount] = v;
  this->swim(this->pqcount);
}

template <typename T>MaxPQ<T>::~MaxPQ()
{
  delete this->datas;
}

int main(int argc, char const *argv[])
{
  MaxPQ<int> a;
  //int input_value;
  a.insert(3);
  a.insert(3);
  a.insert(234);
  a.insert(2341);
  a.insert(9999);
  std::cout << a.delmax() << ' ' << a.delmax() << std::endl;
  std::cout << a.delmax() << ' ' << a.delmax() << std::endl;
  std::cout << a.delmax() << std::endl;
  return 0;
}

堆排序,在实现了优先队列的基础上,就并不难了,只要数组先构建为有限队列,然后在依次执行出列操作,得到的一组数据就已经排序,同时我们还可以进一步简化,只用sink()方法就可以来构建一个堆,下面是一个堆排序的实现:

#include <iostream>

template <typename T>void sink(T *datas,int index,int size)
{
  T new_node = datas[index];
  while (index<<1 <= size)
  {
    int j = index<<1;
    if (j < size && datas[j] < datas[j+1])
    {
      j++;
    }
    if (new_node > datas[j])
    {
      break;
    }
    datas[index] = datas[j];
    index = j;
  }
  datas[index] =new_node;
}

template <typename T>void heap_sort(T datas, int size)
{
  //构造二叉堆
  for (int k = size<<1;k>0;k--)
  {
    sink(datas, k, size);
  }
  //排序
  while(size > 1)
  {
    std::swap(datas[1],datas[size--]);//取出最大值
    sink(datas, 1, size);//重新构造二叉堆
  }
}


int main(int argc, char const *argv[])
{
  int a[5] = {0,1,3,4,1};
  heap_sort(a,5);
  for (auto i:a)
  {
    std::cout << i  << std::endl;
  }
  return 0;
}

快速排序,听起来很高大上,其实快速排序并不总是能够实现最快的排序。但在大多数情况下,快速排序是一个优秀的排序算法,可以实现O(nlgn)(线性对数级别)的时间复杂度。

快速排序的中心思想,也是分治法,其内容为:在这组数据中选取一个基准,将一组数据中大于这个基准的数作为一组,其余为另一组,再分别对两组数据进行排序。最后就可以得到已排序的数组。
快速排序在开始时,与归并排序一样需要许多额外的空间来分别存放大于基准的数和小于基准的数。后来人们提出了一个改进方法,将其可以成为一个原地算法,于是其优势便得以展现出来了,下面是一个原始版本的快速排序:
#include

using namespace std;

template <typename T> void quick_sort(T *datas, int size)
{
  if (size < 2)
  {
    return;
  }
  T before[size], after[size], mid;//这里创建了两个数组
  int before_index = 0, after_index = 0, index = 0;
  mid = datas[index++];
  while (index<size) {
    if (mid>datas[index])
    {
      before[before_index++] = datas[index++];
    }
    else
    {
      after[after_index++] = datas[index++];
    }
  }
  quick_sort(before, before_index);
  quick_sort(after, after_index);
  index = 0;
  //int i = 0;
  for (int i = 0;i<before_index;i++)
  {
    datas[index++] = before[i];
  }
  datas[index++] = mid;
  for (int i = 0;i<after_index;i++)
  {
    datas[index++] = after[i];
  }
  return;
}

int main(int argc, char const *argv[]) {
  int a[]={234,234,61,12,5,31,6,3,2354,1,1243,63};
  quick_sort(a, sizeof(a)/sizeof(int));
  for (auto i:a)
  {
    std::cout << i << ' ';
  }
  std::cout << std::endl;
  return 0;
}

可以看到这个版本使用了两个数组,导致这个算法要求的空间复杂度很高,于是就有了下面这种原地版本的改进:

template <typename T>void swap(T &a, T&b)
{
  T temp = a;
  a = b;
  b = temp;
}

template <typename T> void quick_sort_inplace(T *datas, int size)
{
  if (size < 2)
  {
    return;
  }
  int before_index = 0;
  int mid = datas[size-1];
  for (int i = 0;i<size-1;i++)
  {
    if (datas[i] > mid)
    {
      swap(datas[before_index++], datas[i]);
    }
  }
  swap(datas[before_index], datas[size-1]);
  quick_sort_inplace(datas, before_index);
  quick_sort_inplace(datas+before_index+1, size-before_index-1);
  return;
}

int main(int argc, char const *argv[]) {
  int a[]={234,234,61,12,5,31,6,3,2354,1,1243,63};
  quick_sort_inplace(a, sizeof(a)/sizeof(int));
  for (auto i:a)
  {
    std::cout << i << ' ';
  }
  std::cout << std::endl;
  return 0;
}

其不同之处就是,并不是使用创建两个新数组的方式来记录比基准大与比基准小的数值,而是选择最后一个数作为基准,然后将较大的值用交换的方法放在数组的前面,并用一个整型变量来记录这个值的大小。下图为一个原地版快速排序的图解:
快速排序

随机背包,顾名思义,是一种随机化的背包。这个背包在与普通背包不同之处在于,每次遍历的顺序都是不一样的。在C++中我们可以通过控制begin函数来使背包进行随机化,这里同时也用到了Fisher–Yates shuffle算法,其核心思想就是从最后一项开始遍历,让每个元素和他本身或前面的一个元素随机交换位置。其代码如下

#include <iostream>
#include <cstdlib>
#include <ctime>

#ifndef INIT_SIZE
#define INIT_SIZE 10
#endif

template <typename T> inline void swap(T &a,T &b)
{
  T temp = a;
  a = b;
  b = temp;
}

template <typename T> class RandomBag
{
public:
  RandomBag();

  bool is_empty();
  int size();
  void add(T data);

  T* begin();
  T* end();

  ~RandomBag();
private:
  T *datas;
  int now_size;
  int max_size;
  void fy_sort();
  void resize(int new_size);
};

template <typename T> void RandomBag<T>::resize(int new_size)
{
  T *new_datas = new T[new_size];
  for (int i=0;i < this->now_size;i++)
  {
    new_datas[i] = this->datas[i];
  }
  delete this->datas;
  this->datas = new_datas;
}

template <typename T> RandomBag<T>::RandomBag()
{
  this->max_size = INIT_SIZE;
  this->now_size = 0;
  this->datas = new T[this->now_size];
}

template <typename T> bool RandomBag<T>::is_empty()
{
  return bool(this->now_size);
}

template <typename T> int RandomBag<T>::size()
{
  return this->now_size;
}

template <typename T> void RandomBag<T>::add(T data)
{
  if (now_size >= max_size)
  {
    this->resize(this->max_size*2);
  }
  this->datas[this->now_size++] = data;
}

template <typename T> void RandomBag<T>::fy_sort()
{
  int i = this->now_size;
  int index;
  if (this->now_size == 0)
  {
    return;
  }
  while (i--) {
    index = rand() % (i+1);
    swap (this->datas[i], this->datas[index]);
  }
}

template <typename T> T* RandomBag<T>::begin()
{
  this->fy_sort();
  return &(this->datas[0]);
}

template <typename T> T* RandomBag<T>::end()
{
  return &(this->datas[this->now_size]);
}

template <typename T> RandomBag<T>::~RandomBag()
{
  delete this->datas;
}

int main(int argc, char const *argv[]) {
  srand (time(NULL));
  RandomBag<int> bag;
  bag.add(3);
  bag.add(7);
  bag.add(2);
  bag.add(1);
  for (auto a : bag)
  {
    std::cout << a << std::endl;
  }
  return 0;
}

归并排序,是一种在元素较少时效率很高的排序算法,其核心思想为将大数组的排序转换为将两个已排序的数组合并问题,如下图所示

归并排序

Swfung8 - 自己的作品,CC BY-SA 3.0

该算法的实现示例如下:

#include <iostream>

template <typename T>void merge_sort(T *arr,int len)
{
if (len < 2)
{
  return;
}
T *left_arr = arr;
int left_len = len/2;
T *right_arr = arr+left_len;
int right_len = len-left_len;
merge_sort(left_arr, left_len);
merge_sort(right_arr, right_len);
int left_index = 0, right_index = 0, temp_index = 0;
T temp[len];
while (left_index < left_len && right_index < right_len)
{
  if (left_arr[left_index] > right_arr[right_index])
  {
    temp[temp_index++] = left_arr[left_index++];
  }
  else
  {
    temp[temp_index++] = right_arr[right_index++];
  }
}
while (left_index < left_len)
{
  temp[temp_index++] = left_arr[left_index++];
}
while (right_index < right_len)
{
  temp[temp_index++] = right_arr[right_index++];
}
for (int i = 0;i < len;i++)
{
  arr[i] = temp[i];
}
}

int main(int argc, char const *argv[]) {
int arr[10]={2,4,6,3,1,3,76,12,634,21};
merge_sort(arr,10);
for (auto i : arr)
{
  std::cout << i <<std::endl;
}
return 0;
}

由于每日总结实在断更得厉害,改为不定期(几天)的总结。


这段时间继续刷算法,并且尝试用我并不熟悉的C去实现一些数据结构,一举两得。算法方面,一直还在基本数据结构的基础上停留,不过通过运用C模板类的编辑,可以在加深这些结构的印象的时候又学会了C类的使用。中途不停犯错不停查,收获良多。双向链表可以双向遍历,相比单向链表要方便很多,下面是使用C模板实现双向链表的类:

template <typename T> struct DoubleNode
{
public:
  T data;
  DoubleNode<T> *prev = nullptr;
  DoubleNode<T> *next = nullptr;
};

template <typename T> class LinkedList
{
private:
  DoubleNode<T> *head_node = nullptr;
  DoubleNode<T> *end_node = nullptr;
public:
  void insert_begin(T insert_data);
  void insert_end(T insert_data);
  void delete_begin();
  void delete_end();
  void insert_node_before(T insert_data, int index);
  void insert_node_after(T insert_data, int index);
  void delete_node(int index);
  T & operator[] (int index);
  ~LinkedList ();
};

template <typename T>void LinkedList<T>::insert_begin(T insert_data)
{
  DoubleNode<T> *new_node = new DoubleNode<T>();
  new_node->data = insert_data;
  new_node->next = this->head_node;
  this->head_node = new_node;
  if (this->end_node == nullptr)
  {
    this->end_node = new_node;
  }
}

template <typename T>void LinkedList<T>::insert_end(T insert_data)
{
  DoubleNode<T> *new_node = new DoubleNode<T>();
  new_node->data = insert_data;
  if (this->head_node==nullptr)
  {
    this->head_node = new_node;
  }
  else
  {
    DoubleNode<T> *in_node = this->head_node;
    while (in_node->next != nullptr)
    {
      in_node = in_node->next;
    }
    in_node->next =new_node;
    in_node->next->prev =new_node;
  }
  this->end_node = new_node;
}

template <typename T>void LinkedList<T>::delete_begin()
{
  if (this->head_node == nullptr)
  {
    return;
  }
  DoubleNode<T> *del_node = this->head_node;
  this->head_node = del_node->next;
  delete del_node;
}

template <typename T>void LinkedList<T>::insert_node_before(T insert_data, int index)
{
  DoubleNode<T> *in_node=this->head_node;
  DoubleNode<T> *new_node = new DoubleNode<T>();
  new_node->data = insert_data;
  if(this->head_node == nullptr && index == 0)
  {
    this->head_node = new_node;
    this->end_node = new_node;
    return;
  }
  for (int i = 1;i<index;i++)
  {
    if (in_node == nullptr)
    {
      return;
    }
    in_node = in_node->next;
  }
  new_node->prev = in_node;
  new_node->next = in_node->next;
  in_node->next->prev = new_node;
  in_node->next = new_node;
}

template <typename T> T& LinkedList<T>::operator[] (int index)
{
  DoubleNode<T> *get_node = this->head_node;
  //if (get_node == nullptr)
  //  return ;
  for (int i=0;i < index ;i++)
  {
    get_node = get_node->next;
  //  if (get_node == nullptr)
  //  {
  //    return;
  //  }
  }
  return get_node->data;
}

template <typename T>void LinkedList<T>::insert_node_after(T insert_data, int index)
{
  DoubleNode<T> *in_node = this->head_node;
  DoubleNode<T> *new_node = new DoubleNode<T>();
  new_node->data = insert_data;
  for (int i = 0;i<index;i++)
  {
    if (in_node == nullptr)
    {
      return;
    }
    in_node = in_node->next;
  }
  new_node->prev = in_node;
  new_node->next = in_node->next;
  in_node->next = new_node;
  if (new_node->next == nullptr)
  {
    this->end_node = new_node;
  }
}

template <typename T>void LinkedList<T>::delete_node(int index)
{
  DoubleNode<T> del_node = this->head_node;
  if (del_node == nullptr)
  {
    return;
  }
  for (int i = 0;i < index;i++)
  {
    del_node = del_node->next;
    if (del_node == nullptr)
    {
      return;
    }
  }
  if (del_node->prev == nullptr)
  {
    this->head_node = del_node->next;
  }
  if (del_node->next == nullptr)
  {
    this->end_node = del_node->prev;
  }
  del_node->prev->next = del_node->next;
  del_node->next->prev = del_node->prev;
  delete del_node;
}

template <typename T> LinkedList<T>::~LinkedList ()
{
  DoubleNode<T> *now_head = this->head_node;
  DoubleNode<T> *del_node = now_head;
  while (del_node != nullptr) {
    now_head = now_head->next;
    delete del_node;
    del_node = now_head;
  }
}

这个链表实现了前插后插,前删后删等功能。下次任务:实现一个随机背包

不知不觉又咸了几天(嗯),在鸽的这段时间里,参加了下学校的比赛,居然还获得了不错的成绩,但也让我进一步加强了对于自己算法基础差的认识。(接下来要好好补补了)

今天做的事情,就是完成了《算法4》上的关于中序表达式补全问题,其实这个问题本身并不难,但由于java可以很方便的创建字符串栈,用c语言创建的就没有这么方便。最后想到再用两个栈,一个栈保存字符串长度,另外一个保存字符的方法来实现与字符串栈同样的效果。下面为代码:

#include <string.h>
#include <stdbool.h>
#include "stacklib.h"
#ifndef INPUT_LENGTH_MAX
#define INPUT_LENGTH_MAX 100
#endif
int main(int argc, char const *argv[]) {
  char str[INPUT_LENGTH_MAX+1];
  bool last_is_num = false;
  fgets (str,INPUT_LENGTH_MAX,stdin);
  str[strlen(str)-1] = '\0';
  stack *str_len_sta = StackCreate ();
  stack *data_sta = StackCreate();
  stack *opt_sta = StackCreate();
  for (int i=0;i<strlen(str);i++)
  {
    switch (str[i]) {
      case '+':
      case '-':
      case '*':
      case '/':
      case '%':
        StackPush(opt_sta,str[i]);
        last_is_num = false;
      break;
      case ')':
      {
        //分别获取之前串的存储长度
        int b_chars_len = StackPop(str_len_sta);
        int a_chars_len = StackPop(str_len_sta);
        //合计字符串长度,3为一对括号和操作符
        int total_len = a_chars_len + b_chars_len + 3;
        //创建数组并保存上两次的串
        char datas[total_len+1];
        datas[0] = '(';
        datas[total_len]='\0';
        for (int i=total_len-2;i>=2+a_chars_len;i--)//由于是栈,所以从后往前读取
        {
          datas[i] = StackPop(data_sta);
        }
        datas[a_chars_len+1] = StackPop(opt_sta);//取出操作符
        for (int i=a_chars_len;i>0;i--)
        {
          datas[i] = StackPop(data_sta);
        }
        datas[total_len-1] = ')';
        datas[total_len] = '\0';
        //重新存入栈中
        for (int i=0;i<total_len;i++)
        {
          StackPush(data_sta, datas[i]);
        }
        StackPush(str_len_sta,total_len);
        last_is_num = false;
      }
      break;
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
      {
        if (last_is_num==true)
        {
          int temp = StackPop(str_len_sta);
          temp++;
          StackPush(str_len_sta,temp);
          StackPush(data_sta,str[i]);
        }
        else
        {
          StackPush(str_len_sta,1);
          StackPush(data_sta,str[i]);
        }
        last_is_num = true;
      }
      break;
      default:
        StackPush(str_len_sta,1);
        StackPush(data_sta,str[i]);
        last_is_num = false;
      break;
    }
  }
  //获取输出长度
  int res_len = StackPop(str_len_sta);
  //获取输出字符串
  char res_str[res_len+1];
  for (int i = res_len-1;i>=0;i--)
  {
    res_str[i] = StackPop(data_sta);
  }
  res_str[res_len] = '\0';
  puts (res_str);

  StackFree(str_len_sta);
  StackFree(data_sta);
  StackFree(opt_sta);
  return 0;
}

看了那么多天的算法,今天转变下心情,学习用python写了一个康威生命游戏(conway Game of Life)。


记得第一次接触到“康威生命游戏”是因为一部番《すべてがFになる THE PERFECT INSIDER》,作品用了很多计算机相关的事物,其中片尾曲的动态演化的康威生命游戏让我记忆犹新。其实所谓生命游戏,不过就是一些点在几条规则下进行演化罢了,但是能从简短的几条规则进行演化为如此复杂的系统着实令人感到有趣。也许这个简单的数学游戏,也隐藏着很深刻的哲学意义。其表明了复杂的结构可以由简单的规则推进。下图为我运行一个”高斯帕斯卡机枪”的截图
!img
代码如下:
#!/usr/bin/python3
#--coding:utf-8--

import argparse
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation


ON = 255
OFF = 0
vals = [ON, OFF]

def getGridStaus(i, j, glid, N):
    return int(glid[i%N][j%N])/ON

def addGlider(i, j, glid):
    glider = np.array([[OFF, OFF, ON],
                       [ON, OFF, ON],
                       [OFF, ON, ON]])
    glid[i:i+3, j:j+3] = glider

def addGosperGun(i, j, glid):
    gun = np.zeros(11*38).reshape(11, 38);
    gun[5][1] = gun[5][2] = 255
    gun[6][1] = gun[6][2] = 255

    gun[3][13] = gun[3][14] = 255
    gun[4][12] = gun[4][16] = 255
    gun[5][11] = gun[5][17] = 255
    gun[6][11] = gun[6][15] = gun[6][17] = gun[6][18] = 255
    gun[7][11] = gun[7][17] = 255
    gun[8][12] = gun[8][16] = 255
    gun[9][13] = gun[9][14] = 255

    gun[1][25] = 255
    gun[2][23] = gun[2][25] = 255
    gun[3][21] = gun[3][22] = 255
    gun[4][21] = gun[4][22] = 255
    gun[5][21] = gun[5][22] = 255
    gun[6][23] = gun[6][25] = 255
    gun[7][25] = 255

    gun[3][35] = gun[3][36] = 255
    gun[4][35] = gun[4][36] = 255

    glid[i:i+11, j:j+38]=gun

def readPattern(file, N):
    content=file.read()
    return np.array(content.split()).astype(int).reshape(N, N)

def randomGrid(N):
    return np.random.choice(vals, N*N, p=[0.2, 0.8]).reshape(N, N)

def update(frameNum, img, grid, N):
    newGrid = grid.copy()
    for i in range(N):
        for j in range(N):
            total = 0
            #计算周围有几个激活的细胞
            for iOffset in range(-1, 1+1):
                for jOffset in range(-1, 1+1):
                    if iOffset != 0 or jOffset != 0:
                        total = total+getGridStaus(i+iOffset, j+jOffset, grid, N)
                        #print ("x=%d,y=%d,io=%d,jo=%d,total=%d"%(i,j,iOffset,jOffset,total));
            if grid[i][j] == ON:
                if total < 2 or total > 3:
                    newGrid[i][j] = OFF
            else:
                if total == 3:
                    newGrid[i][j] = ON
    img.set_data(newGrid)
    grid[:] = newGrid[:]
    return img

def main():
    #构造参数解析器
    parser = argparse.ArgumentParser(description="Running for Conway's Game of Life simulation")
    #创建互斥参数组
    group = parser.add_mutually_exclusive_group()
    #解析参数
    parser.add_argument('--grid-size', dest='N', type=int, required=False)
    parser.add_argument('--mov-file', dest='movfile', required=False)
    group.add_argument('--pattern-file', dest='patternfile', required=False)
    parser.add_argument('--interval', dest='interval', required=False)
    group.add_argument('--glider', action='store_true', required=False)
    group.add_argument('--gosper', action='store_true', required=False)
    args = parser.parse_args()
    #设置模拟大小
    N = 100
    if args.N and int(args.N) > 8:
        N = int(args.N)
    #设置更新间隔
    updateInterval = 50
    if args.interval:
        updateInterval = int(args.interval)
    #设置初始条件
    grid = np.array([])
    if args.glider:#当设置了glider标志位时使用滑翔机初始化
        grid = np.zeros(N*N).reshape(N, N)
        addGlider(1, 1, grid)
    elif args.gosper and N > 40:#使用高斯帕滑翔机枪初始化
        grid= np.zeros(N*N).reshape(N,N)
        addGosperGun(1,1,grid)
    elif args.patternfile:#使用文件初始化
        file = open(args.patternfile)
        N = int(file.readline())
        grid=readPattern(file,N)
        #print (grid)
    else:#否则使用随机初始化
        grid = randomGrid(N)
    #设置动画
    fig, ax = plt.subplots()
    img = ax.imshow(grid, interpolation='nearest')
    ani = animation.FuncAnimation(fig, update,
                                  fargs=(img, grid, N, ),
                                  frames=10,
                                  interval=updateInterval,
                                  save_count=100)
    if args.movfile:
        ani.save(args.movfile, fps=30, extra_args=['-vcodec', 'libx264'])
    plt.show()
if __name__ == '__main__':
    main()

今天继续对于栈的用法进行学习,实现了一个检查括号是否匹配的程序。但我觉得在switch语句上应该还有优化空间的,代码如下,用到了之前我构建的stacklib库:

#include "stacklib.h"

#include <string.h>
#ifndef INPUT_MAX_LENGTH
#define INPUT_MAX_LENGTH 50
#endif

int main(int argc, char const *argv[]) {
  char str[INPUT_MAX_LENGTH+1];
  fgets (str,INPUT_MAX_LENGTH,stdin);
  stack *brackets=StackCreate();
  bool is_match=true;
  for (int i=0;i<strlen(str);i++)
  {
    if (strchr("{[(",str[i])!=NULL)
    {
      StackPush(brackets,str[i]);
    }
    if (strchr("}])",str[i])!=NULL)
    {
      char pop_ch,compare_ch;
      pop_ch=StackPop(brackets);
      if (pop_ch==POP_ERR)
        is_match=false;
        switch (pop_ch) {
          case '(':
          compare_ch=')';
          break;
          case '[':
          compare_ch=']';
          break;
          case '{':
          compare_ch='}';
          break;
          default:
          break;
        }
        if (compare_ch!=str[i])
        is_match=false;
      }
    }
    if (!StackIsEmpty(brackets))
    {
      is_match=false;
    }
    puts (is_match?"TRUE":"FALSE");
    return 0;
  }

今天双十一买的《C专家编程》到了~
买了当然就看了看,虽然名字起得挺高大上的,但是内容还算实在,是一些C语言中的细节和容易犯的错误
可以看到,连ANSI C标准都是有互相矛盾的地方,要自己写出一个没有bug的程序是多么难Orz,就连一个sizeof (int)*p的真实含义,也可以让人纠结很长时间。

除了程序员疏忽容易引起的bug,以及各种 C语言本身设计初就犯的错误容易引起bug,如运算符的.的优先级高于*,()高于*,==高于位运算符等,这些运算符优先顺序都是反直觉的,规避的有效方法便是在其基础上加括号。

今天用链表实现了队列,我今天认识到这么一个道理,所有可读写的数据结构,都是可以用来模拟其他数据结构的,毕竟都是作为存储的工具。而最基本的两种数据结构就是数组和链表,分别对应线式结构和链式结构,但是只有数据结构正确的搭配,才能实现效率最大化。比如我可以用两个栈的API来实现一个队列,但是这样虽然实现了,但是效率极低。只有合理运用数据结构,才能构建高效的算法。

今天在C语言中实现了一个基于链表的栈,由于链表不需要像动态数组那样拷贝值,理论上来说在经常插入和删除较多数据时,其性能应该优于动态数组。但是在我的实际操作中发现,用链表实现的栈要比动态数组的实现低效很多。

经过一番查找后发现,链表拖慢的原因是频繁的malloc和free的开销太大,所以在实际使用中,很少有这样直接对链表的使用,而是采用内存池的策略。所谓内存池的策略,就是自己一次分配一块大内存空间,然后自己实现类似malloc和free的函数,从而达到减少对malloc和free的调用。实际的高级语言中,大部分时候都是使用动态数组而不是使用链表的的,如Java里的ArrayList,C++里的vector。or。